目录
文件描述符限制
文件描述符惟一性
Socket
哪些是进程
输出/入重定向、管道
文件描述符文件
Linux一切皆文件
在Linux操作系统中,可以将一切都看作是文件,包括普通文件、目录文件、字符设备文件(鼠标、鼠标)、块设备文件(硬碟、光驱)、套接字等等linux 查看进程文件linux 查看进程文件,所有的一切都具象成文件,并提供了统一的插口,便捷应用程序的调用。
文件描述符
平时所谓的打开一个文件,虽然都可以称为获取到了文件描述符(Filedescriptor),简称为fd,当应用程序恳求操作系统内核打开/新建一个文件时,内核会返回一个文件描述符用户对标这个被打开/新建的文件,虽然fd本质上就是一个非负整数,读写文件都须要使用这个文件描述符来指定待读写的文件。
实际上,文件描述符为一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开/新建一个文件时,内核向进程返回一个文件描述符。不过文件描述符这一概念只适用于UNIX、Linux这样的操作系统。
内核
操作系统为每一个进程维护了一个文件描述符表,该表的索引值都是从0开始的linux查看端口占用,因而在不同的进程中可以看见相同的文件描述符指向同一个文件,也可能指向不同的文件。
fd、句柄、i-node关系图
通过上图可以听到,当不同进程中出现相同的文件描述符时,可能实际对应的文件并不是同一个,相反不同进程中不同的文件描述符也可可能对应同一个文件
通常来说,一个进程启动时会从files[0]
读取输入,将输出写入files[1]
,将错误信息写入files[2]
。(files链表包含该进程打开的文件表针)
举个反例,以我们的角度C语言的printf
函数是向命令行复印字符,并且从进程的角度来看,就是向files[1]
写入数据;同理,scanf
函数就是进程企图从files[0]
这个文件中读取数据。
每位进程被创建时,files
的前三位被填入默认值,分别指向标准输入流、标准输出流、标准错误流。我们常说的「文件描述符」就是指这个文件表针链表的索引,所以程序的文件描述符默认情况下0是输入,1是输出,2是错误。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号
对应关系
通过前面的关系图,可以得到以下的关系:
进程领到文件的文件描述符ID等于领到进程文件描述符表的索引==》通过索引领到文件表针,指向了系统级文件描述符表的文件偏斜量==》再通过文件偏斜量找到inode表针,最终对应到真实的文件
文件描述符限制
文件描述符是非常重要的系统资源,理论上系统显存多大就可以打开多少个文件描述符,但实际情况是内核会有系统级/用户级限制,这是为了避免某个进程消耗掉所有的文件资源,可以通过ulimit-n查看,通常是65535
文件描述符惟一性
进程+文件描述符ID确认,由于内核为每一个进程都维护了一份其所属的文件描述符表
Socket
Socket也叫套接字,它也是文件。当Server端窃听到有联接时,应用程序会恳求内核创建Socket,Socket创建好后会返回一个文件描述符给应用程序,当有数据包经过网卡时,内核会针对不同的传输方法(TCP、UDP)通过数据包的源IP、源端口、目地IP、目地端口(即四元组)等信息在内核维护的一个ipcb单向数组中找到对应的Socket,并将数据包形参到该Socket的缓冲区,应用程序恳求读取Socket中的数据时,内核都会将数据拷贝到应用程序的显存空间,因而完成对Socket数据的读取。
进程哪些是进程
具象地说,计算机可以看成是这样的:
十分具象!
整个显存空间被界定为用户空间和内核空间。
用户空间:装载着用户进程须要使用的资源
内核空间:储存着内核进程须要加载的系统资源,通常情况下这种资源用户是不容许访问的。并且非常情况进程会共享一些内核空间的资源,比如动态链接库等等。
当我们用go写一个helloword后,经过编译可以得到一个可执行文件,在命令行运行就可以复印出一句helloword,之后程序退出。在操作系统层面,就是新建了一个进程,之后该进程将经过编译后的可执行文件读入显存空间,之后执行,最退后出。编译好后的可执行文件仅仅是一个文件,并不是进程,可执行文件必须载入显存red hat linux,包装成一个进程能够真正的跑去来。进程是借助操作系统创建的,每位进程都有固定的属性,例如进程号(PID)、进程状态、打开的文件等等,进程创建好以后,读入你的程序,你的程序才被系统执行
Linux进程数据结构:
// Linux源码
struct task_struct {
// 进程状态
long state;
// 虚拟内存结构体
struct mm_struct *mm;
// 进程号
pid_t pid;
// 指向父进程的指针
struct task_struct __rcu *parent;
// 子进程列表
struct list_head children;
// 存放文件系统信息的指针
struct fs_struct *fs;
// 一个数组,包含该进程打开的文件指针
struct files_struct *files;
......
};
其中task_struct
就是Linux内核对于一个进程的描述,也可以称为进程描述符。mm
指向的是进程的虚拟显存,files
表针指向一个链表,这个字段里装着所有该进程打开文件的表针,可以通过以下命令查看当前进程所包含的文件。
ls /proc/$$/fd
输出/入重定向、管道
再来看一个图
进程与文件描述符
假如看了前面文件描述符的讲解,就可以挺好理解输出/入重定向、管道的操作了。
$ command < file.txt
只需将file[0]指向一个文件,这么进程都会从这个文件中读取数据,而不是从外设按键
$ command > file.txt
只需将file[1]指向一个文件,这么进程输出就不会写入到显示器,而是写入到这个文件。
$ cmd1 | cmd2 | cmd3
管线符也许就是将一个进程的输出流和另一个进程输入流联接上去产生一条「管道」
以下的内容是基于Linux的,不同操作系统进程和线程的内容不同。
从Linux内核的角度来说,进程和线程没有被区别对待,为何如此说呢?由于无论是进程还是线程,都是用前面提及的task_struct
结构来表示,它们惟一的区别就在于共享的数据区域不同,而通过fork()
创建的进程和父进程之间的数据内容是拷贝而不是像线程之间的共享。
即将由于线程是共享了进程的数据,所以在多线程程序中才须要借助锁机制,防止多个线程同时往同一区域对数据进行操作造成数据的错乱。
Refer