做全英文网站,宣传片拍摄制作多少钱,泉州模板自助建站,重庆互联网公司排行榜文章目录 1. open 接口介绍1.1 代码演示1.2 open 函数返回值 2. 文件描述符 fd2.1 0 / 1 / 22.2 文件描述符的分配规则 3. 重定向3.1 dup2 系统调用函数 4. FILE 与 缓冲区 1. open 接口介绍
使用 man open 指令查看手册#xff1a;
#include sys/types.h
#include … 文章目录 1. open 接口介绍1.1 代码演示1.2 open 函数返回值 2. 文件描述符 fd2.1 0 / 1 / 22.2 文件描述符的分配规则 3. 重定向3.1 dup2 系统调用函数 4. FILE 与 缓冲区 1. open 接口介绍
使用 man open 指令查看手册
#include sys/types.h
#include sys/stat.h
#include fcntl.hint open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);pathname: 要打开或创建的目标文件
flags: 打开文件时可以传入多个参数选项用下面的一个或者多个常量进行“或”运算构成flags。
参数:O_RDONLY: 只读打开O_WRONLY: 只写打开O_RDWR : 读写打开这三个常量必须指定一个且只能指定一个O_CREAT : 若文件不存在则创建它。需要使用mode选项来指明新文件的访问权限O_APPEND: 追加写
返回值成功新打开的文件描述符失败-1open 函数具体使用哪个和具体应用场景有关。如目标文件不存在需要 open 创建则第三个参数表示创建文件的默认权限否则使用两个参数的 open。
write read close lseek 类比 C 文件相关接口。
1.1 代码演示
操作文件除了使用 C 语言的接口【Linux】回顾 C 文件接口还可以采用系统接口来进行文件访问
写文件
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include string.hint main()
{umask(0);int fd open(myfile, O_WRONLY | O_CREAT, 0644);if (fd 0){perror(open);return 1;}int count 5;const char* msg hello open!\n;int len strlen(msg);while (count--){write(fd, msg, len);// fd : 下面介绍// msg : 缓冲区首地址// len : 本次读取期望写入多少个字节的数据// 返回值 : 实际写了多少字节数据}close(fd);return 0;
}读文件
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include string.hint main()
{int fd open(myfile, O_RDONLY);if (fd 0){perror(open);return 1;}const char* msg hello open!\n;char buf[1024];while (1){ssize_t s read(fd, buf, strlen(msg)); // 类比writeif (s 0){printf(%s, buf);}else{break;}}close(fd);return 0;
}1.2 open 函数返回值
在认识返回值之前先来认识两个概念系统调用 和 库函数
fopen fclose fread fwrite 都是 C 标准库当中的函数我们称之为库函数libc而 open close read write lseek 都属于系统提供的接口称之为系统调用接口 系统调用接口与库函数的关系如上图所以可以认为f# 系列的函数都是对系统调用的封装方便二次开发。
2. 文件描述符 fd
文件描述符的本质就是数组下标
2.1 0 / 1 / 2
Linux 进程默认情况下会有 3 个缺省打开的文件描述符分别是标准输入 0标准输出 1标准错误 2012 对应的物理设备一般是键盘显示器显示器所以输入输出也可以采用如下方式
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include string.hint main()
{char buf[1024];ssize_t s read(0, buf, sizeof(buf));if (s 0){buf[s] 0;write(1, buf, strlen(buf));write(2, buf, strlen(buf));}return 0;
}现在我们知道文件描述符就是从 0 开始的小整数当我们打开文件时操作系统在内存中要创建相应的数据结构来描述目标文件于是就有了 file 结构体表示一个已经打开的文件对象而进程执行 open 系统调用就必须让进程和文件关联起来每个进程都有一个指针 *files 指向一张表 files_struct 该表最重要的部分就是包含一个指针数组每个元素都是一个指向打开文件的指针所以本质上文件描述符就是该数组的下标只要拿着文件描述符就可以找到对应的文件。
2.2 文件描述符的分配规则
直接看代码
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hint main()
{int fd open(myfile, O_RDONLY);if (fd 0){perror(open);return 1;}printf(fd: %d\n, fd);close(fd);return 0;
}输出发现是 fd: 3
关闭 0 或者 2再看
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hint main()
{close(0);//close(2);int fd open(myfile, O_RDONLY);if (fd 0){perror(open);return 1;}printf(fd: %d\n, fd);close(fd);return 0;
}发现结果是fd: 0 或者 fd: 2
可见文件描述符的分配规则在 files_struct 数组当中找到当前没有被使用的最小的一个下标作为新的文件描述符会分配给最新打开的文件。
3. 重定向
那如果关闭 1 呢看代码
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.hint main()
{close(1);int fd open(myfile, O_WRONLY | O_CREAT, 00644);if (fd 0){perror(open);return 1;}printf(fd: %d\n, fd);fflush(stdout);close(fd);exit(0);
}此时我们发现本来应该输出到显示器上的内容输出到了文件 myfile 当中其中 fd 1。这种现象叫做输出重定向。
常见的重定向有 。
那重定向的本质是什么呢 3.1 dup2 系统调用函数
函数原型如下
#include unistd.h
int dup2(int oldfd, int newfd);函数简介
makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:
将newfd设置为oldfd的副本并在必要时先关闭newfd但请注意以下事项:* If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.如果oldfd不是有效的文件描述符则调用失败newfd不会关闭。* If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.如果oldfd是一个有效的文件描述符并且newfd与oldfd具有相同的值那么dup2()什么都不做并返回newfd。示例代码
#include stdio.h
#include unistd.h
#include fcntl.hint main()
{int fd open(./log, O_CREAT | O_RDWR, 0644);if (fd 0){perror(open);return 1;}close(1);dup2(fd, 1);int i 0;for (i 0; i 5; i){char buf[1024] { 0 };ssize_t read_size read(0, buf, sizeof(buf) - 1);if (read_size 0){perror(read);break;}printf(%s, buf);fflush(stdout);}return 0;
}printf 是 C 库当中的 IO 函数一般往 stdout 中输出但是 stdout 底层访问文件的时候找的还是 fd:1 但此时 fd:1 下标所表示的内容已经变成了 log 的地址不再是显示器文件的地址所以输出的任何消息都会往文件中写入进而完成输出重定向。
4. FILE 与 缓冲区
因为 IO 相关函数与系统调用接口对应并且库函数封装系统调用所以本质上访问文件都是通过 fd 访问的。所以 C 库当中的 FILE 结构体内部必定封装了 fd。缓冲区就是一块内存区域其存在目的是为了提升使用者的效率用空间换时间。我们这里说的缓冲区是语言层面的缓冲区也就是 C 自带的缓冲区跟内核中的缓冲区没有关系。缓冲区刷新方式 无缓冲 - 无刷新行缓冲 - 行刷新 写满一行才刷新我们平时写代码经常会遇到缓冲区的问题全缓冲 - 全部刷新在普通文件中写入时缓冲区被写满才刷新强制刷新使用各种方法让缓冲区强制刷新如fflush() 函数自动刷新程序退出的时候会自动刷新。
来段代码研究一下
#include stdio.h
#include string.hint main()
{const char* msg0 hello printf\n;const char* msg1 hello fwrite\n;const char* msg2 hello write\n;printf(%s, msg0);fwrite(msg1, strlen(msg0), 1, stdout);write(1, msg2, strlen(msg2));fork();return 0;
}运行出结果
hello printf
hello fwrite
hello write但如果对进程实现输出重定向呢./a.out file 我们发现结果变成了
hello write
hello printf
hello fwrite
hello peintf
hello fwrite我们发现 printf 和 fwrite库函数都输出了 2 次而 write 只输出了一次系统调用。
为什么呢肯定和 fork 有关
一般 C 库函数写入文件是全缓冲的而写入显示器是行缓冲。printf fwrite 库函数会自带缓冲区进度条例子可以说明【Linux】编写第一个小程序进度条当发生重定向到普通文件时数据的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据就不会被立即刷新即使是 fork 之后但是进程退出之后会统一刷新写入文件当中。但是 fork 的时候父子数据会发生写时拷贝所以当你父进程准备刷新的时候子进程也就有了同样的一份数据随即产生两份数据。write 没有变化说明没有所谓的缓冲。
综上printf fwrite 库函数会自带缓冲区而 write 系统调用没有带缓冲区。另外我们这里所说的缓冲区都是用户级缓冲区。其实为了提升整机性能OS 也会提供相关内核级缓冲区不过不在我们讨论范围之内。那这个缓冲区谁提供呢printf fwrite 是库函数writre 是系统调用库函数在系统调用的“上层”是对系统调用的“封装”但是 write 没有缓冲区而 printf fwrite 有足以说明该缓冲区是二次加上的又因为是 C所以由 C 标准库提供。 END