在read();write();等对文件操作的这些系统调用中,我们会看到这样一个整数,fd(file descriptor),它就是文件描述符;
文件描述符到底是什么?
对于一个进程来说,它可以打开多个文件,而操作系统中又有多个进程,所以存在大量文件的打开与关闭,读与写....对于具体的一个进程而言,它打开的文件是需要组织的,操作系统承担了这一任务。
每个进程对应一个PCB块,在真正的代码里叫做struct task_struct{ };它们里面有一个结构体指针,指向一个指针数组,这个数组中每一个元素都是一个地址,是一个结构体的地址,具体而言:这个结构体描述的是文件( 操作系统规定,默认情况下,前三个地址分别是键盘,显示器,显示器的地址,分别称为标准输入,标准输出,标准错误),它们分别对应这个指针数组的下标是0 1 2;
【注】:Linux下所有的I/O设备都被抽象为文件,你要输出或者读入,其实是针对这些文件的读与写;
你要打开文件,你使用了系统调用open,你要创建文件,你使用create();
如上图,每一个进程都有自己的PCB,都有自己的文件描述符表格(也就是这个数组),而这个数数组的下标就是fd;
它是数组下标,那它的范围就是大于等于零,即非负;
对于一个进程来说,默认0 1 2已经有地址了,你再打开一个文件,fd对应就是3,即索引为3的指针数组元素存放了一个文件的地址,就是你打开的这个文件的地址;
【注】:文件在Linux中被抽象为struct file{};这样一个结构体,就连键盘,显示器这样的硬件也不例外,所谓一切皆文件;
我们提到的这个struct file{};这里面记录着被打开文件的所有信息,包括这个文件的inode,文件位置,打开模式....等信息;
用户使用系统调用 open
()或者 creat
()来打开或创建一个文件,用户态得到的结果值就是 fd;这个fd是怎么来的?OS在这个过程中做了什么?
它们的返回值就是fd(当然,如果有错误发生,就会返回-1);
值得注意的是:这个fd是就小分配的,OS会初始化这个文件的struct file{};在里面填入文件的inode以及打开模式,以及初始化这个文件的读写位置为0,最后在fd_array[ ]中填入地址,然后把fd返回给应用程序(即调用open/write的那个进程),你之后对于这个文件的所有操作,拿着这个fd来标识一个文件;
【关于就小分配】:比如你close(1);即你关闭了1号描述符,它本来对应标准输出到显示器,那么你再打开一个文件时,它的文件描述符就是1了,因为就小而言,0被标准输入占着;
有了这个文件描述符表,OS就明确知道哪个进程打开了哪些文件,有利于更好管理资源;
当你使用close();来关闭一个文件时,内核会释放上述数据结构,fd也将可以被其他文件分配;
当然一个进程退出时,它打开的所有文件(struct file{};fd....)占用的内存资源都会被释放;
写在最后:
这个文件描述符可以和重定向联系起来,例如dup2();等就是复制了指针数组的地址,从而索引对应下标时,地址不再指向原来的文件;
dup2将会拷贝oldfd为下标的地址到newfd的下标的地址;