【Linux】文件描述符 - fd
https://i-blog.csdnimg.cn/blog_migrate/46100371baae9bdee5949b47912c6908.gif#pic_center1. open 接口介绍
利用 man open 指令查察手册:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int 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: 追加写
返回值:
成功:新打开的文件描述符
失败:-1
open 函数详细利用哪个,和详细应用场景有关。如:目标文件不存在,需要 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.h>
int 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.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if (fd < 0)
{
perror("open");
return 1;
}
const char* msg = "hello open!\n";
char buf;
while (1)
{
ssize_t s = read(fd, buf, strlen(msg)); // 类比write
if (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 都属于体系提供的接口,称之为体系调用接口;
https://i-blog.csdnimg.cn/blog_migrate/1aa9d03b8603907d938103190b23e843.png#pic_center
[*]体系调用接口与库函数的关系如上图;
[*]以是,可以认为,f# 系列的函数,都是对体系调用的封装,方便二次开发。
2. 文件描述符 fd
[*]文件描述符的本质,就是数组下标!!!
2.1 0 / 1 / 2
[*]Linux 历程默认情况下会有 3 个缺省打开的文件描述符,分别是标准输入 0,标准输出 1,标准错误 2;
[*]0,1,2 对应的物理设备一样平常是:键盘,显示器,显示器;
[*]以是输入输出也可以采用如下方式:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf;
ssize_t s = read(0, buf, sizeof(buf));
if (s > 0)
{
buf = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
https://i-blog.csdnimg.cn/blog_migrate/ef5a3abe4f044ae2d126f8444897bac8.png#pic_center
[*]现在我们知道,文件描述符就是从 0 开始的小整数;
[*]当我们打开文件时,操作体系在内存中要创建相应的数据结构来描述目标文件,于是就有了 file 结构体,表示一个已经打开的文件对象;
[*]而历程实行 open 体系调用,就必须让历程和文件关联起来;
[*]每个历程都有一个指针 *files ,指向一张表 files_struct ,该表最紧张的部门就是包含一个指针数组,每个元素都是一个指向打开文件的指针;
[*]以是,本质上,文件描述符就是该数组的下标,只要拿着文件描述符,就可以找到对应的文件。
2.2 文件描述符的分配规则
直接看代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int 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.h>
int 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.h>
int 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。这种现象叫做输出重定向。
常见的重定向有:> ,>> ,< 。
那重定向的本质是什么呢?
https://i-blog.csdnimg.cn/blog_migrate/59476aec8eb1dcda6416ada19e15810a.png#pic_center
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.h>
int 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 = { 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.h>
int 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
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]