chapter5:linux文件函数-open、read、write、close、lseek、dup、fcntl

    科技2022-08-10  109

    linux文件函数

    虚拟地址空间、PCB与文件描述符虚拟地址空间PCB文件描述符C库函数和系统函数的关系 查看man文档的方法open函数read函数write函数close函数open,read,write,close的使用lseek函数dup函数,文件描述符的复制fcntl函数

    linux文件函数所作的操作就是读写文件,在linux中描述文件的方式是文件描述符,文件描述符存在与pcb中,而pcb又在虚拟地址空间的内核区中,所以要先了解虚拟地址空间,pcb的概念。

    虚拟地址空间、PCB与文件描述符

    虚拟地址空间

    linux会为每一个正在运行的程序(进程)分配一块0~4G的地址空间(虚拟地址空间)

    虚拟地址空间由 用户区 和内核区两部分组成

    用户区包含: 代码段、已初始化的全局变量、未初始化的全局变量、堆(从下往上)、共享库、栈(从上往下)、环境变量内核区包含:内存管理、进程管理、设备驱动管理、VFS虚拟文件系统;内核区是受保护的,用户不能对该空间进行读写 注意:虚拟地址空间不是直接存在于内存上的,它是通过MMU(内存管理单元)映射到内存上的,这里有个概念是进程的4G虚拟地址空间不是立即分配的,是虚拟的,是在使用过程中被映射到内存上的。

    为什么使用虚拟地址空间:

    方便编译器和操作系统安排程序的地址分布。 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区方便进程之间的隔离 不同进程使用的虚拟地址彼此隔离。一个进程的代码无法更改另一个进程的物理内存方便os使用你那可怜的内存 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存供应量变小时,内存管理器会将物理内存页(通常大小为4kb)保存到磁盘文件。数据或代码页会根据需要在物理内存和磁盘之间移动。

    PCB

    在内核空间中,有一个结构体被称为PCB(进程控制块)这个结构体包含进程的许多信息:

    进程 id,系统中每个进程都有一个唯一的 id,C 语言中用 pid_t 类型表示进程状态, 就绪态,运行态,挂起态,终止态进程切换是需要保存和恢复的一些 cpu 寄存器描述虚拟地址空间的信息描述控制终端的信息当前工作目录umask 掩码文件描述符表,包含很多指向 file 结构体的指和信号相关的信息用户 id 和组 id 会话(session)和进程组进程可以使用的资源上限(resource limit)

    这个结构体的定义可以在/usr/src/linux-headers-$(uname -r)/include/linux/sched.h中看到

    文件描述符

    pcb中包含一个文件描述符表—一个1024大小的数组,包含着1024个文件描述符,文件描述符是一个整型数,他的本质是一个指针,数和指针一一对应,指向一个结构体,结构体包含打开的文件的描述信息

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

    查看man文档的方法

    man man 这里需要知道的是1,2,3点,

    1是可执行文件与shell命令2是系统调用3是库函数调用

    open函数

    使用 man 2 open查看man文档 传入参数:

    第一个为文件名第二个为flags:O_RDONLY,O_WRONLY,O_RDWR,O_APPEND,O_CREAT,O_TRUNC等 当flags为O_CREAT时,需要输入mode

    返回值:

    返回新的文件描述符,总是可用的最小的文件描述符错误返回-1,可使用perror(const char *s)打印错误原因

    open函数的几种使用方式

    fd = open("filename", O_RDWR); //以读写方式打开文件 fd = open("filename", O_RDWR|O_CREAT, 0777); //创建文件并以读写方式打开 fd = open("filename", O_RDWR|O_CREAT|O_EXCL, 0777); //文件如果不存在则创建 fd = open("filename", O_RDWR|O_TRUNC); //将文件截断为0,即删除文件中的内容

    read函数

    man 2 read 从文件描述符fd中读取count个字节到buf中,在支持seeking的文件中,从文件指针出开始读,并且随着读的指针增加,读到末尾返回0

    传入参数:

    fd :文件描述符buf :缓冲区,自己创建的一个数组,保存输入数据,是一个传出参数count: 读入count个字符

    返回值:

    0: 读到的字节数

    =0, 不是错误,可能是由于读到文件末尾,读管道,读终端或者被信号打断-1, 出错

    write函数

    man 2 write

    传入参数:

    fd 文件描述符buf 要写入的数据count 写入数据的字节数

    返回值:

    失败返回-1成功返回写入的字节数,如果写入的字节数小于需要写入的字节数,那么可能是由于磁盘满了,写管道阻塞等原因

    close函数

    man 2 close 传入参数:

    fd 需要关闭的文件描述符

    传出参数:

    0 关闭成功-1 失败

    open,read,write,close的使用

    open_write.c

    0 #include <stdio.h> 1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <unistd.h> 5 6 int main(void) 7 { 8 int fd,ret; 9 10 fd = open("hello", O_RDWR | O_CREAT, 0777); //创建打开文件 11 if(fd == -1) //判断是否出错 12 { 13 perror("open error:"); 14 return -1; 15 } 16 printf("fd = %d\n", fd); //打印fd 17 ret = close(fd); //关闭文件 18 if(ret == -1) 19 { 20 perror("close error:"); 21 return -1; 22 } 23 return 0; 24 }

    copy.c

    0 #include <stdio.h> 1 #include <unistd.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <stdlib.h> 6 7 void sys_err(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 int main(void) 13 { 14 int fd[2], ret, count; 15 char buf[2048]={0}; 16 17 //打开源文件 18 fd[0] = open("src.txt", O_RDONLY); 19 if(fd[0] == -1) 20 sys_err("open src error:"); 21 22 //创建目标文件 23 fd[1] = open("des.txt", O_RDWR|O_CREAT, 0777); 24 if(fd[1] == -1) 25 sys_err("open des error:"); 26 27 count = read(fd[0], buf, sizeof(buf)); //读取一个buf 28 29 while(count) //文件读完返回0 30 { 31 write(fd[1], buf, count); //写入count个字节,因为最后count可能小于buf的大小 32 count = read(fd[0], buf, sizeof(buf)); 33 } 34 35 ret = close(fd[0]); 36 ret = close(fd[1]); 37 38 return 0; 39 }

    lseek函数

    man 2 lseek lseek 函数将重置fd的指针,移动到相对于whence的offset个字节的位置 传入参数:

    fd: 文件描述符offset: 相对偏移量,相对与谁将有whence决定whence:决定offset相对偏移的位置 SEEK_SET:offset相对于文件的起始位置偏移,offset也就是相当是绝对偏移量SEEK_CUR:相对于当前的位置偏移SEEK_END:相对于文件末尾的位置偏移

    返回值:

    成功返回相对于文件开头的偏移量失败返回-1

    函数作用

    获取文件大小,当offset为0,whence为SEEK_END,函数返回的为文件的大小移动文件指针,这是最基本的作用文件拓展,函数可以相对于文件末尾的位置向后移动,可以拓展文件大小,文件末尾需要写入一个字符

    文件拓展实例

    0 #include <stdio.h> 1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <stdlib.h> 5 #include <sys/types.h> 6 #include <unistd.h> 7 8 void sys_err(const char *s) 9 { 10 perror(s); 11 exit(-1); 12 } 13 14 int main(void) 15 { 16 int fd, ret; 17 //创建一个文件 18 fd = open("hole", O_RDWR|O_CREAT, 0644); 19 if(fd == -1) 20 sys_err("open err:"); 22 //向后拓展2047 23 ret = lseek(fd, 2047, SEEK_END); 24 if(ret == -1) 25 sys_err("lseek err:"); 26 //写操作是必须的,否则拓展不成功 27 write(fd, "a", 1); 28 printf("file size is %d\n", ret); 29 30 close(fd); 31 }

    dup函数,文件描述符的复制

    man 2 dup

    dup函数 将创建oldfd文件描述符的一个复制,返回可用的最小文件描述符,新旧文件描述符使用同一个指针,使用这两个文件描述符写操作时不会相互覆盖

    dup2函数 将旧的文件描述符oldfd复制到newfd,如果newfd是一个被打开的文件描述符,那么在复制之前会自动静默关闭,新的文件描述符newfd和旧的指向同一个文件,常用在重定向输出

    返回值

    成功 返回新的文件描述符失败 返回-1

    重定向输出到文件

    0 #include <stdio.h> 1 #include <unistd.h> 2 #include <stdlib.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 7 void sys_err(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 13 int main(void) 14 { 15 int fd, ret; 16 17 fd = open("output", O_RDWR|O_CREAT, 0644); //创建一个文件 18 if(fd == -1) 19 perror("open err:"); 20 21 ret = dup2(fd, STDOUT_FILENO); //重定向标准输出到文件 22 23 printf("hello, world\n"); //输出点什么 24 25 close(fd); 27 }

    新旧文件描述符使用同一个指针

    0 #include <stdio.h> 1 #include <unistd.h> 2 #include <stdlib.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 7 void sys_err(const char *s) 8 { 9 perror(s); 10 exit(-1); 11 } 12 13 int main(void) 14 { 15 int fd, newfd, ret; 16 17 fd = open("interchange", O_RDWR|O_CREAT, 0644); 18 if(fd == -1) 19 perror("open err:"); 20 //复制文件描述符 21 newfd = dup(fd); 22 //分别用新旧文件描述符写入,不会相互覆盖 23 write(fd, "write by oldfd\n", sizeof("write by oldfd\n")); 24 write(newfd, "write by newfd\n", sizeof("write by newfd\n")); 25 26 close(fd); 27 close(newfd); 28 29 }

    fcntl函数

    fcntl函数功能比较强大,常用的是获得/设置文件状态标记,直接查man文档吧,和之前的使用大同小异 man 2 fcntl

    fcntl的常用功能

    Processed: 0.019, SQL: 8