小伙伴们,这篇博客对应的官方文档在此处。建议大家对照着官方文档来阅读我的博客,这样效果会更好。OK,开始我们的学习。
进程隔离
官网上有那么一句话:
1 | 进程隔离也是很多新手经常遇到的问题。修改了全局变量的值,为什么不生效,原因就是全局变量在不同的进程,内存空间是隔离的,所以无效。 |
如果小伙伴们之前对进程没有概念,要理解起来还是有一定难度的。所以在这里,我有必要说一下进程是什么东西。
简单来说,进程是一个数据结构,数据结构里面存放了很多成员变量,这个是最本质的。很多书说:
1 | 进程是资源的集合 |
那么这句话所说的资源指的就是这些变量存放的东西,集合指的就是进程这个数据结构。
如果还是有一些抽象,不要紧,我们去看看Linux中进程这个数据结构的定义是怎样的。数据结构的定义在这里,大概是在593行的位置:
我列举几个这个数据结构里面我们听的比较多的成员变量:
1 | struct task_struct { |
其中,pid
变量里面保存着这个进程的标识,也就是说给进程标一个号,区分一下各个进程。通过children
变量,操作系统可以找到这个进程的所有子进程。变量files
里面有一个变量fdtab
,通过fdtab
里面的fd
指针,可以找到当前进程打开的所有文件,如下图所示:
操作系统会为每一个进程分配一个task_struct
数据结构,一旦CPU执行某个进程的代码的时候,操作系统把当前进程的这些变量提供给CPU。因为每个进程都有自己的这个task_struct
数据结构,所以每个进程的变量是在各自的进程里面的,因此不同进程的变量是隔离的,这些变量也包括全局变量、文件句柄(即上图中的fd)。
紧接着,官网又给出那么一句话:
1 | 不同进程的文件句柄是隔离的,所以在A进程创建的Socket连接或打开的文件,在B进程内是无效,即使是将它的fd发送到B进程也是不可用的 |
这句话什么意思呢?为了搞清楚,我们要知道文件句柄的作用。
当用户调用open系统调用(或者其他打开文件的系统调用)的时候,内核会创建一个打开文件对象来表示该文件的一个打开实例。内核同时也会分配一个文件描述符(也就是上图中的fd)作为打开文件对象的句柄。如下图所示:
open系统调用向进程返回这个文件描述符,然后这个文件描述符就被存放在file array
里面了。然后进程就可以通过这个fd1
来找到对应的文件。那么,图中的offset是什么意思呢(我们把offset叫做打开文件对象)?在Unix系统中,文件默认是顺序访问的。当用户打开文件的时候,内核初始化这个offset的偏移指针为0。举个例子,如果当前进程刚打开一个文件(文件内容是字符串abcdefghijklnm
),那么此时offset的状态如下:
因为offset的偏移指针为0,所以指向字母a。所以,当我们通过fd去读取文件内容的时候,读取到的第一个字符就是字母a
。假设我连续读了3个字节,那么此时的状态应该是这个样子的:
也就是说,当进程从文件里面读取3个字节之后,offset此时指向字母d。当进程下一次读取文件的时候,读取出来的第一个字母就是d了。这就是offset的概念。
同一个进程还可以多次打开同一个文件,如下图所示:
可以看出,虽然fd1和fd2都是指向同一个文件,但是,如果进程通过fd1去读取文件的话,读到的第一个字母是d;如果进程通过fd2去读取文件的话,读取到的第一个字母是a。所以,每个文件描述符代表着与文件的一个独立会话,对应的打开文件对象(即图中的offset)保存者该会话的内容,这包括了被打开文件的模式(只读、只写、读写)以及下一次读取或者写入时的偏移指针。
我们通过代码来直观感受一下:
1 |
|
结果如下:
验证了我们的说法。
正是因为offset这个打开文件对象对同一个文件的不同会话进行了隔离,所以,才有了官网的这句话:
1 | 不同进程的文件句柄是隔离的,所以在A进程创建的Socket连接或打开的文件,在B进程内是无效,即使是将它的fd发送到B进程也是不可用的 |
也就是说,就算fd是一样的,但是offset是不一样的。
其实到这里算是讲完了隔离,但是,我还想再讲一点其他的东西。
即,同一个进程是否可以让多个fd指向同一个offset,从而达到多个fd共享同一个offset的效果呢?答案是可以做到。
进程可以通过dup系统调用来复制一个文件描述符fd,这样一来,两个文件描述符共享着相同的会话:
代码如下:
1 |
|
(注意,这段代码运行不了,因为PHP没有提供dup这个函数)
假设,这个函数是可以运行,那么输出的结果应该是
1 | The process reads three bytes through handle1, the content is: abc |
下面是更新的内容,经过热心网友 @许怀远的指正,unix domain socket可以用来在不同的进程之间传递fd,而且传过去后还可以正常使用。至于如何使用unix domain socket,小伙伴们可以在《UNIX网络编程》卷一的第15章找到答案。