《UNIX环境高级编程》第3-4章 系统函数总结(open read write lseek stat 链接函数 目录函数 dup dup2 fcntl unix文件系统介绍)

前言:linux下文件权限设置中的数字表示:

https://blog.csdn.net/u013063153/article/details/53113979

chmod ABC file

其中A、B、C各为一个数字,分别表示User、Group、及Other的权限。

A、B、C这三个数字如果各自转换成由“0”、“1”组成的二进制数,则二进制数的每一位分别代表一个角色的读、写、运行的权限。比如User组的权限A:

如果可读、可写、可运行,就表示为二进制的111,转换成十进制就是7。
如果可读、可写、不可运行,就表示为二进制的110,转换成十进制就是6。
如果可读、不可写、可运行,就表示为二进制的101,转换成十进制就是5。

一些人说的“4=r,2=w,1=x”的意思是:
r 代表读,w 代表写,x 代表执行,
如果可读,权限是二进制的100,十进制是4;
如果可写,权限是二进制的010,十进制是2;
如果可运行,权限是二进制的001,十进制是1;

具备多个权限,就把相应的 4、2、1 相加就可以了:
若要 rwx 则 4+2+1=7
若要 rw- 则 4+2=6
若要 r-x 则 4+1=5
若要 r-- 则 =4
若要 -wx 则 2+1=3
若要 -w- 则 =2
若要 --x 则 =1
若要 --- 则 =0

为不同的角色分配不同的权限,放在一起,就出现 777、677这样的数字了。 

c库函数

image

文件描述符

image

虚拟地址空间

image

系统函数和C库函数的关系

image

1.系统IO函数

1.1一些概念

文件描述符
PCB
C库函数的缓冲区

1.2 open函数

打开方式
  必选项:
    #define O_RDONLY	     00
    #define O_WRONLY	     01
    #define O_RDWR	         02
    还有O_EXEC和O_SEARCH,不常见
  可选项:
    O_CREAT 
	文件权限:本地有一个掩码,使用此选项时,open函数需同时说明第3个参数mode(openat函数需说明第4个参数mode),用mode指定该新文件的访问权限(涉及进程umask修改)
	创建文件后发现权限不是777,为什么呢?
	因为O_CREAT创建文件时文件权限内含一个本地掩码,文件的实际权限=给定权限与(&)取反(~)本地掩码
	可以通过umask命令得到本地掩码,然后计算出实际权限
	例:给定权限777,本地掩码0002
	则给定权限为111111111
	本地掩码取反111111101
	与操作(&) 111111101
	实际权限为775,实际上是操作系统对文件的保护机制。Ubuntu键入umask 022 ,则掩码修改为022,掩码可修改
	iv. 判断文件是否存在:参数O_EXCL
    O_APPEND
    O_ASYNC
    O_CLOEXEC

    O_DIRECT
    O_TRUNC
    等等
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main()
{
    int fd;
    // 打开已经存在的文件
    // 1.文件路径+文件名 2.打开方式
    fd = open("hello.c", O_RDWR);
    if (fd == -1) {
        perror("open file");
        exit(1);
    }
    printf("打开已经存在的文件 fd=%d\n", fd); // 0 1 2 分别为标准输入输出和标准错误

    // 创建新文件
    fd = open("myhello.c", O_RDWR | O_CREAT, 0777); // 0表示8进制,可以不写
    if (fd == -1) {
        perror("open new file");
        exit(1);
    }
    printf("创建新文件并打开 fd=%d\n", fd);

    // 判断文件是否已存在 O_CREAT | O_EXCL 两个联合使用
    // open("myhello.c", O_RDWR | O_CREAT | O_EXCL, 0777);

    // 文件截断为0: 相当于清空文件内容
    // open("myhello.c", O_RDWR | O_TRUNC, 0777);

    // 关闭文件
    int ret = close(fd);
    printf("ret = %d\n", ret);
    if (ret == -1) {
        perror("close file");
        exit(1);
    }
    return 0;
}

1.3 read函数

返回值
	1. -1 读文件失败
	2. 0文件读完了
	3. >0 读取的字节数

1.4 write函数

参数
	1. fd文件描述符
	2. 缓冲区,自提供
	3. 写入多少字节
返回值
	1. -1 写文件失败
	2. 正常整型数据:表示成功写入多少字节
	3. >0 读取的字节数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    // 打开一个已经存在的文件
    int fd = open("lianxi.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(1);
    }

    // 创建一个新文件 -- 写操作
    int fd1 = open("newfile", O_CREAT | O_WRONLY, 0664); // 0一定要加,保证权限可以是指定的
    if (fd == -1) {
        perror("open1");
        exit(1);
    }

    // 开始读
    char buf[1024] = {0}; // 1 char = 1 byte; 这里1次最多读1kb
    int count = read(fd, buf, sizeof(buf));
    if (count == -1) {
        perror("read");
        exit(1);
    }
    // 1次不可能读1kb,循环读
    while (count != 0) {
        // 将读出的数据写入另一个文件中
        int ret = write(fd1, buf, count);
        printf("wtitint byte %d ...\n", count);
        printf("wtite byte %d success\n", ret);
        // 继续读文件
        count = read(fd, buf, sizeof(buf));
    }
    // 关闭文件
    close(fd);
    close(fd1);
}

1.5 lseek函数

https://blog.csdn.net/qq_35733751/article/details/80709783
image

入参:
1) off_t是int类型数
	2) fd文件描述符
	3) offset文件指针偏移量
	4) whence有三个值
		a) SEEK_SET:将读写位置指向文件头后再增加offset个位移量。
		欲将读写位置移到文件开头时:lseek(int fildes,0,SEEK_SET);
		b) SEEK_CUR:以目前的读写位置往后增加offset个位移量。
		欲将读写位置移到文件尾时:lseek(int fildes,0,SEEK_END);
		c) SEEK_END:将读写位置指向文件尾后再增加offset个位移量。
		想要取得目前文件位置时:lseek(int fildes,0,SEEK_CUR);
5) 返回值:
		a) 当调用成功时则返回目前的读写位置,也就是距离文件开头多少个字节。若有错误则返回-1,errno 会存放错误代码。
		b) 可能设置erron的错误代码:
			i) EBADF:fildes不是一个打开的文件描述符。
			ii) ESPIPE:文件描述符被分配到一个管道、套接字或FIFO。
			iii) EINVAL:whence取值不当。
作用
	1. 获取文件大小
	2. 移动文件指针
	3. 文件拓展:只能向后拓展,中间位置或者尾部向后,不能向前拓展。将文件变长
返回值:新的偏移量(成功),-1(失败)
	#define SEEK_SET	0	/* Seek from beginning of file.  */
	#define SEEK_CUR	1	/* Seek from current position.  */
	#define SEEK_END	2	/* Seek from end of file.  */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
    // 打开一个已经存在的文件
    int fd = open("aa.txt", O_RDWR); // 注意:这里不能写O_RDONLY,因为后面要write
    if (fd == -1) {
        perror("open");
        exit(1);
    }

    // 获取文件的长度
    int ret = lseek(fd, 0, SEEK_END);
    printf("文件长度 file length = %d\n", ret);

    // 文件拓展
    ret = lseek(fd, 2000, SEEK_END);
    printf("文件拓展后:ret = %d\n", ret);

    // 实现文件拓展,需要在最后做一次写操作 ???
    /*
    char buf[1] = {'a'};
    ret = write(fd, buf, sizeof(buf));
    printf("buf size=%d\n", sizeof(buf));
    printf("buf content=%s\n", buf);
    printf("写文件长度 %d\n", ret);
    */
    ret = write(fd, "a", 1);
    if (ret == -1) {
        perror("wrtie");
        exit(1);
    }
    printf("写文件长度 %d\n", ret);
    // 关闭文件
    close(fd);
}

2.操作文件属性

2.1 stat函数

linux 命令:
image

目标:通过stat函数,自己实现ls -l 这个命令的功能
image

获取文件属性(从i节点获取)
返回值:成功0,失败-1
文件属性(参数2):

image

image

特性:能够穿透(跟踪)符号链接:

st_mode

	st_mode:
		1) 该变量占2byte共16位,整型
		2) 掩码的使用st_mode &(与运算)掩码(8进制)(获得全部权限需要&掩码,如果是单一权限则直接用前面的即可)
		3) 其他人权限(0-2bit)(掩码:S_IRWXO 00007过滤st_mode中除其他人权限以外的信息)
			a) S_IROTH 00004
			b) S_IWOTH 00002
			c) S_IXOTH 00001
		4) 所属组权限(3-5bit)(掩码:S_IRWXU 00070过滤st_mode中除所属组权限以外的信息)
			a) S_IRGRP 00040
			b) S_IWGRP 00020
			c) S_IXGRP 00010
		5) 文件所有者权限(6-8bit)(掩码:S_IRWXU 00700过滤st_mode中除所有者权限以外的信息)
			a) S_IRUSR 00400
			b) S_IWUSR 00200
			c) S_IXUSR 00100
		6) 特殊权限位(9-11bit)(很少用)
			a) S_ISUID 0004000 设置用户ID
			b) S_ISGID 0002000 设置组ID
			c) S_ISVTX 0001000 黏着位
		7) 文件类型(12-15bit)(掩码:S_IFMT 0170000过滤st_mode中除文件类型以外的信息)
		与掩码做与运算过滤掉除文件类型以外的信息得到文件类型的八进制码与下面七个文件类型比对即可找出是哪个文件类型
			a) S_IFSOCK 0140000 套接字
			b) S_IFLINK 0120000 符号链接(软链接)
			c) S_IFREG  0100000普通文件
			d) S_IFBLK 0060000 块设备
			e) S_IFDIR 0040000目录
			f) S_IFCHR 0020000字符设备
			g) S_IFIFO 0010000 管道

image

#include<stdio.h>
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<stdlib.h>
#include<pwd.h>
#include<grp.h>

// argc参数个数,argv数组
int main(int argc,char *argv[])
{
    if (argc < 2 ) {
        printf("./a.out filename\n");
        exit(1);
    }
    struct stat st;
    int ret = stat(argv[1], &st);
    if(ret == -1) {
        perror("stat");
        exit(1);
    }
    // 存储文件类型和访问权限
    char perms[11] = {0};
    // 判断文件类型
    // S_IFMT : 0170000
    switch (st.st_mode & S_IFMT) {
    case S_IFLNK:
        perms[0]='1';
        break;
    case S_IFDIR:
        perms[0]='d';
        break;
    case S_IFBLK:
        perms[0]='b';
        break;
    case S_IFCHR:
        perms[0]='c';
        break;
    case S_IFSOCK:
        perms[0]='s';
        break;
    case S_IFIFO:
       perms[0]='P';
        break;
    default:
       perms[0]='?';
        break;
    }
    // 判断文件的访问权限
    // 文件所有者
    perms[1] = (st.st_mode & S_IRUSR) ? 'r':'-';
    perms[2] = (st.st_mode & S_IRUSR) ? 'w':'-';
    perms[3] = (st.st_mode & S_IRUSR) ? 'x':'-';
    // 文件所属组
    perms[4] = (st.st_mode & S_IRGRP) ? 'r':'-';
    perms[5] = (st.st_mode & S_IRGRP) ? 'w':'-';
    perms[6] = (st.st_mode & S_IRGRP) ? 'x':'-';
    // 其他人
    perms[7] = (st.st_mode & S_IROTH) ? 'r':'-';
    perms[8] = (st.st_mode & S_IROTH) ? 'w':'-';
    perms[9] = (st.st_mode & S_IROTH) ? 'x':'-';
    // 硬链接计数
    int linkNum =st.st_nlink;
    // 文件所有者
    char* fileUser = getpwuid(st.st_uid)->pw_name;
    // 文件所属组
    char* fileGrp = getgrgid(st.st_gid)->gr_nmae;
    // 文件大小
    int filesize = (int)st.st_size;
    // 修改时间
    char* time = ctime(&st.st_mtime);
    char mtime[512] ={0};
    strncpy(mtime,time,strlen(time)-1);
    char buf[1024];
    sprintf(buf,"%s  %d   %s   %s   %d   %s %s",perms,linkNum,fileUser,fileGrp,filesize,mtime,argv[1]);
    printf("%s\n",buf);
    return 0;
}

PS:i节点说明,详细参考:UNIX 环境高级编程 3.10 4.14两节
image

lstat与stat的区别

如果文件类型不是符号链接(软链接),二者没有区别;
如果是符号链接,state函数:穿透(追踪)函数 ———软连接,即根据软链接追踪到所需执行的文件查看大小
lstate函数:不穿透(不追踪)函数 ———直接读软链接文件的大小

chmod函数

	i. 作用:改变文件的权限
	ii. 原型:int chmod(const char *filename, mode_t pmode)
		1) 参数:
			a) filename -->文件名
			b) pmode -->权限(必须是一个8进制数)(可用strtol函数传入一个数转换为八进制数)

chown函数

	i. 作用:chown改变文件所有者(用户ID和组ID),fchown文件操作,lchown不穿透
	ii. 参数:(在/etc/passwd下找uid和pid)
		1) path文件路径
		2) owner文件所有者uid
		3) group文件所属组gid

truncate函数

文件截断(lseek也能实现文件拓展,将指针偏移量偏移n来实现,但是要额外执行一步写操作,truncate则不需要。)	
	i. 作用:将参数path指定的文件大小改为参数length指定的大小。如果原来的文件大小比参数length的大,则超过的部分会被删去
	ii. int truncate(const char *path, off_t length);
		1) path文件路径
		2) length指定的文件长度 :如果文件本身长为100,length指定为20,那么后面80长度的字符被截断,只保留前面20个字符。如果指定为300,则文件被扩展,后面扩展的内容以空洞(@符号)的形式显示
	link函数:
		1) 作用:创建一个硬链接
		2) 原型:int link(const char *oldpath, const char *newpath);
	symlink函数:作用:创建一个软链接(符号链接)
	readlink函数:作用:读软连接对应的文件名,不是读内容。
		readlink(const char*path,char*buf,size_t bufsize)只能读软链接,buf读出来的是软链接所指文件的绝对路径。
	unlink函数:int unlink(const char *pathname)
		1) 作用:删除一个文件的目录项并减少它的链接数,若成功返回0,失败返回-1,错误信息存与errno。
		2) 如果想要通过调用这个函数来成功删除文件,你必须拥有这个文件的所属目录的写(w)和执行(x)权限
		3) 使用:
			a) 如果是符号链接,删除符号链接
			b) 如果是硬链接,硬链接数减1,当减为0时,释放数据块和inode
			c) !!!如果文件硬链接为0,但有进程已打开该文件,并持有文件描述符,则等进程关闭该文件时,kernel才真正去删除该文件
				i) 利用该特性创建临时文件,先open或creat创建一个文件,马上unlink此文件				

目录操作函数

i. 目录操作函数:
	i. chdir函数:(与getcwd结合一起理解)
		1) 修改当前进程的路径
		2) 原型:int chdir(const char *path);
	ii. getcwd函数:
		1) 获取当前进程工作目录
		2) 原型:char* getcwd(char *buf,size_t size);
	iii. mkdir函数:
		1) 作用:创建目录
		2) !!!注意!!!:创建的目录需要有执行权限,否则无法进入目录
		3) 原型:int mkdir(const char *pathname,mode_t mode);
	iv. rmdir函数:
		1) 作用:删除一个空目录
		2) 函数原型:int rmdir(const char *pathname);
	v. opendir函数 man 3
		1) 作用:打开一个目录
		2) 原型:DIR* opendir(const *name);
		3) 返回值:
			a) DIR结构指针,该结构是一个内部结构,保存所打开的目录信息,作用类似于FILE结构(openfile -->read--->write--->close)
			b) 函数出错返回NULL
	vi. readdir函数 man 3
	!!!(指定一个while循环,遍历目录中的文件,将文件信息通过返回值反馈直至反馈NULL值即遍历完成,当在遍历过程中又遇到目录,则进入目录继续遍历,对树状结构遍历最好的方法即递归)
		1) 作用:读目录
		2) 函数原型:struct dirent *readdir(DIR *dirp);
		3) 返回值(返回一条记录项)
					//ino 此目录进入点的inode
		//off 目录文件开头至此目录进入点的位移,偏移量
		//d_reclen d_name(文件名)的长度,不包含NULL字符
		//d_type d_name所指文件类型
		//d_name[NAME_MAX+1] 文件名
		4) d_type:
			a) DT_BLK 块设备
			b) DT_CHR 字符设备
			c) DT_DIR 目录
			d) DT_LNK 软链接
			e) DT_FIFO 管道
			f) DT_REG 普通文件
			g) DT_SOCK 套接字
			h) DT_UNKOWN 未知
		5) -D_BSD_SOURCE 编译时添加宏定义
	v.closedir man 3
	关闭目录
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<dirent.h>
//定义一个函数实现递归功能
int getFileNum(char* root)
{
    //打开目录
    DIR* dir = NULL;
    dir = opendir(root);
    int total = 0;
    if(dir == NULL)   //如果目录能打开,返回的肯定是非0值
    {
        perror("opendir");
        exit(1);
    }
    //遍历当前打开的目录
    struct dirent* ptr = NULL;//因为readdir是一个结构体类型的指针,所以我们要定义一个这样的指针
    char path[1024] = {0};
    while((ptr = readdir(dir)) != NULL)
    {
        //过滤掉. 和 ..
        if(strcmp(ptr->d_name,".")==0 || strcmp(ptr->d_name,"..")==0)
            continue;
        if(ptr->d_type == DT_DIR)
        {
            //递归 读目录
            sprintf(path, "%s/%s"), root, ptr->d_name;
            total += getFileNum(path);
        }
        //如果是普通文件
        if(ptr->d_type == DT_REG)
        {
            total++
        }
    }
    closedir(dir);
    return total;
}
int main(int argc,char* argv[])
{
    if(argc < 2)
    {
        printf("./a.out dir\n");
        exit(1);
    }
    int total=getFileNum(argv[1]);
    printf("%s has file numbers %d\n", argv[1], total);
    return 0;
}

dup和dup2函数 文件描述符的复制

 dup、,dup2函数
	作用:复制现有的文件描述符(重定向文件描述符)
	int dup(int oldfd);
		返回值是文件描述符中没有被占用的最小的文件描述符。
		(由前面的文件描述符的知识可知,0、1、2是已经被系统占用(常开)的文件描述符,那么文件会从第三个文件描述符开始使用,例如该文件使用的是第三个文件描述符,那么使用了dup函数后,将第四个文件描述符(未占用且最小)复制给他)
	int dup2(int oldfd, int newfd);
		将old复制给new:
		如果new是一个被打开的文件描述符,在拷贝之前先关掉new,
		如果old和new是同一个文件描述符,则不会关闭而是返回同一个文件描述符old。

dup示例:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
    int fd = open("a.txt",O_RDWR);
    if(fd==-1)
    {
        perror("open");
        exit(1);
    }
    printf("file open fd = %d\n",fd);
    //找到进程文件描述表中 ==第一个== 可用的文件描述符
    //将参数指定的文件复制到该描述符后,返回这个描述符
    int ret=dup(fd);
    if(ret==-1)
    {
        perror("dup");
        exit(1);
    }
    printf("dup fd =%d\n",ret);
    char *buf = "你是猴子搬来的救兵吗????\n";
    char *buf1="你大爷的,我是程序猿!!!\n";
    write(fd,buf,strlen(buf));
    write(ret,buf1,strlen(buf1));
    close (fd);
    return 0;
}

dupw示例:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
    int fd = open("english.txt",O_RDWR);
    if(fd==-1)
    {
        perror("open");
        exit(1);
    }
    int fd1 = open("a.txt",O_RDWR);
    if(fd1==-1)
    {
        perror("open");
        exit(1);
    }
    printf("file open fd = %d\n",fd);
    printf("file open fd1 = %d\n",fd1);
    //找到进程文件描述表中 ==第一个== 可用的文件描述符
    //将参数指定的文件复制到该描述符后,返回这个描述符
    int ret=dup2(fd1,fd);
    if(ret==-1)
    {
        perror("dup2");
        exit(1);
    }
    printf("current fd =%d\n",ret);
    char *buf = "主要看气质!!!!!!!\n";
    write(fd,buf,strlen(buf));
    write(fd1,"hello,world!",13);
    close (fd);
    close (fd1);
    return 0;
}

fcntl 改变已经打开的文件属性

k. fcntl函数:
	i. 作用:改变已经打开的文件的属性,例如打开文件的时候(文件为只读),修改文件的时候,如果要添加追加O_APPEND,可使用fcntl在文件打开时修改。
	ii. 原型:
		1) int fcntl(int fd,int cmd);
		2) int fcntl(int fd,int cmd,long arg);!!!
		3) int fcntl(int fd,int cmd,struct flock *lock);
	iii. 功能:
		1) 复制一个现有的描述符 -- cmd F_DUPFD
		2) 获得/设置文件描述符标记 -- cmd F_GETFD/F_SETFD
		3) 获得/设置文件标志标记(打开文件时追加)!!!
			a) cmd F_GETFL
				i) 只读打开 O_RDONLY
				ii) 只写打开 O_WRONLY
				iii) 读写打开 O_RDWR
				iv) 执行打开 O_RXEC
				v) 搜索打开目录 O_SEARCH
				vi) 追加写 O_APPEND
				vii) 非阻塞模式 O_NONBLOCK
			b) cmd F_SETFL 可更改的几个标识
				i) O_APPEND
				ii) O_NONBLOCK、O_SYNC、O_DSYNC等 
		4) 获得/设置异步I/O所有权 --cmd F_GETOWN/F_SETOWN
		5) 获取/设置记录锁 -- cmd 
			a) F_GETLK
			b) F_SETLK
			c) F_SETLKW

fcntl示例

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
    int fd;
    int flag;
    // 测试字符串
    char *p = "我们是一个有中国特色社会主义的国家!!!!!!";
    char *q = "社会主义好!!!";
    // 只写的方式打开文件
    fd = open("test.txt", O_WRONLY);
    if (fd == -1)
    {
        perror("open");
        exit(1);
    }
    // 输入新的内容,该部分会覆盖原来的旧内容
    if (write(fd, p, strlen(p))==-1)
    {
        perror("write");
        exit(1);
    }
    //使用F_GETFL命令得到文件状态标志
    flag = fcntl(fd, F_GETFL, 0);
    if (flag ==-1)
    {
        perror("fcntl");
        exit(1);
    }
    /* 将文件状态标准添加“追加些”选项,追加写就是文件指针在文件末尾写,WRONLY是在文件
    开头写,所以每次写都会覆盖原先的内容 */
    flag |= O_APPEND;
    // 将文件状态修改为追加写
    if (fcntl(fd, F_SETFL, flag) == -1)
    {
        perror("fcntl --append write");
        exit(1);
    }
    // 再次输入新内容,该内容会追加到就内容后面
    if (write(fd, q, strlen(q)) == -1)
    {
        perror("write again");
        exit(1);
    }
    // 关闭文件
    close(fd);
    return 0;
    // 最后功能为:一大段文本内容中,第一句话覆盖掉了文本中的字符,第二段话追加到文本最末尾处。
}
posted @ 2022-09-18 10:17  胖白白  阅读(227)  评论(0编辑  收藏  举报