小夥伴們,這篇部落格對應的官方文件在此處。建議大家對照著官方文件來閱讀我的部落格,這樣效果會更好。OK,開始我們的學習。

程序隔離

官網上有那麼一句話:

程序隔離也是很多新手經常遇到的問題。修改了全域性變數的值,為什麼不生效,原因就是全域性變數在不同的程序,記憶體空間是隔離的,所以無效。

如果小夥伴們之前對程序沒有概念,要理解起來還是有一定難度的。所以在這裡,我有必要說一下程序是什麼東西。

簡單來說,程序是一個

資料結構

,資料結構裡面存放了很多成員變數,這個是最本質的。很多書說:

程序是資源的集合

那麼這句話所說的資源指的就是這些變數存放的東西,集合指的就是程序這個資料結構。

如果還是有一些抽象,不要緊,我們去看看Linux中程序這個資料結構的定義是怎樣的。資料結構的定義在這裡,大概是在593行的位置:

《就是要你懂swoole》-- 程式設計須知

我列舉幾個這個資料結構裡面我們聽的比較多的成員變數:

struct task_struct {

pid_t pid;

struct list_head children;

struct files_struct *files;

}

其中,

pid

變數裡面儲存著這個程序的標識,也就是說給程序標一個號,區分一下各個程序。透過

children

變數,作業系統可以找到這個程序的所有子程序。變數

files

裡面有一個變數

fdtab

,透過

fdtab

裡面的

fd

指標,可以找到當前程序開啟的所有檔案。如果我們追蹤Linux的原始碼,檢視它的初始化程式碼,會有下圖所示的關係:

《就是要你懂swoole》-- 程式設計須知

作業系統會為每一個程序分配一個

task_struct

資料結構,一旦CPU執行某個程序的程式碼的時候,作業系統把當前程序的這些變數提供給CPU。因為每個程序都有自己的這個

task_struct

資料結構,所以每個程序的變數是在各自的程序裡面的,因此不同程序的變數是隔離的,

這些變數也包括全域性變數、檔案控制代碼(即上圖中的fd)

緊接著,官網又給出那麼一句話:

不同程序的檔案控制代碼是隔離的,所以在A程序建立的Socket連線或開啟的檔案,在B程序內是無效,即使是將它的fd傳送到B程序也是不可用的

這句話什麼意思呢?為了搞清楚,我們要知道檔案控制代碼的作用。

當用戶呼叫open系統呼叫(或者其他開啟檔案的系統呼叫)的時候,核心會建立一個開啟檔案物件來表示該檔案的一個開啟例項。核心同時也會分配一個檔案描述符(也就是上圖中的fd)作為開啟檔案物件的控制代碼。如下圖所示:

《就是要你懂swoole》-- 程式設計須知

open系統呼叫向程序返回這個檔案描述符,然後這個檔案描述符就被存放在

file array

裡面了。然後程序就可以透過這個

fd1

來找到對應的檔案。那麼,圖中的offset是什麼意思呢(我們把offset叫做

開啟檔案物件

)?在Unix系統中,檔案預設是順序訪問的。當用戶開啟檔案的時候,核心初始化這個offset的偏移指標為0。舉個例子,如果當前程序剛開啟一個檔案(檔案內容是字串

abcdefghijklnm

),那麼此時offset的狀態如下:

《就是要你懂swoole》-- 程式設計須知

因為offset的偏移指標為0,所以指向字母a。所以,當我們透過fd去讀取檔案內容的時候,讀取到的第一個字元就是字母

a

。假設我連續讀了3個位元組,那麼此時的狀態應該是這個樣子的:

《就是要你懂swoole》-- 程式設計須知

也就是說,當程序從檔案裡面讀取3個位元組之後,offset此時指向字母d。當程序下一次讀取檔案的時候,讀取出來的第一個字母就是d了。這就是offset的概念。

同一個程序還可以多次開啟同一個檔案,如下圖所示:

《就是要你懂swoole》-- 程式設計須知

可以看出,雖然fd1和fd2都是指向同一個檔案,但是,如果程序透過fd1去讀取檔案的話,讀到的第一個字母是d;如果程序透過fd2去讀取檔案的話,讀取到的第一個字母是a。所以,每個檔案描述符代表著與檔案的一個獨立會話,對應的開啟檔案物件(即圖中的offset)儲存者該會話的內容,這包括了被開啟檔案的模式(只讀、只寫、讀寫)以及下一次讀取或者寫入時的偏移指標。

我們透過程式碼來直觀感受一下:

<?php

$handle1 = fopen(“file。txt”, “r”);

$handle2 = fopen(“file。txt”, “r”);

$content = fread($handle1, 3);

echo “The process reads three bytes through handle1, the content is: ” 。 $content 。 PHP_EOL;

$content1 = fread($handle1, 1);

$content2 = fread($handle2, 1);

echo “The process reads one bytes through handle1, the content is: ” 。 $content1 。 PHP_EOL;

echo “The process reads one bytes through handle2, the content is: ” 。 $content2 。 PHP_EOL;

結果如下:

《就是要你懂swoole》-- 程式設計須知

驗證了我們的說法。

正是因為offset這個開啟檔案物件對同一個檔案的不同會話進行了隔離,所以,才有了官網的這句話:

不同程序的檔案控制代碼是隔離的,所以在A程序建立的Socket連線或開啟的檔案,在B程序內是無效,即使是將它的fd傳送到B程序也是不可用的

也就是說,

就算fd是一樣的,但是offset是不一樣的

其實到這裡算是講完了隔離,但是,我還想再講一點其他的東西。

即,同一個程序是否可以讓多個fd指向同一個offset,從而達到多個fd共享同一個offset的效果呢?答案是可以做到。

程序可以透過dup系統呼叫來複制一個檔案描述符fd,這樣一來,兩個檔案描述符共享著相同的會話:

《就是要你懂swoole》-- 程式設計須知

程式碼如下:

<?php

$handle1 = fopen(“file。txt”, “r”);

$handle2 = dup($handle1);

$content = fread($handle1, 3);

echo “The process reads three bytes through handle1, the content is: ” 。 $content 。 PHP_EOL;

$content1 = fread($handle1, 1);

$content2 = fread($handle2, 1);

echo “The process reads one bytes through handle1, the content is: ” 。 $content1 。 PHP_EOL;

echo “The process reads one bytes through handle2, the content is: ” 。 $content2 。 PHP_EOL;

(注意,這段程式碼執行不了,因為PHP沒有提供dup這個函式)

假設,這個函式是可以執行,那麼輸出的結果應該是

The process reads three bytes through handle1, the content is: abc

The process reads one bytes through handle1, the content is: d

The process reads one bytes through handle2, the content is: e

下面是更新的內容,經過熱心網友 @許懷遠的指正,unix domain socket可以用來在不同的程序之間傳遞fd,而且傳過去後還可以正常使用。至於如何使用unix domain socket,小夥伴們可以在《UNIX網路程式設計》卷一的第15章找到答案。