标準輸入 0 标準輸出 1 标準錯誤 2 重定向
管道可以将一個指令的輸出導向另一個指令的輸入,進而讓兩個(或者更多指令)像流水線一樣連續工作,不斷地處理文本流。
ps 檢視 程序建立:kernel并不提供直接建立新程序的。剩下的所有程序都是init程序通過fork機制建立的。新的程序要通過老的程序複制自身得到,這就是fork。fork通常作為一個函數被調用。這個函數會有兩次傳回,将子程序的PID傳回給父程序,0傳回給子程序。所有程序的PPID父程序都是指向init程序。 pstree 顯示init程序樹
每個程序都會屬于一個程序組(process group),每個程序組中可以包含多個程序。程序組會有一個程序組上司程序 (process group leader),上司程序的PID ,成為程序組的ID (process group ID, PGID),以識别程序組。上司程序可以先終結。此時程序組依然存在,并持有相同的PGID,直到程序組中最後一個程序終結。
在shell支援工作控制(job control)的前提下,多個程序組還可以構成一個會話 (session)。bash(Bourne-Again shell)支援工作控制,而sh(Bourne shell)并不支援。 會話是由其中的程序建立的,該程序叫做會話的上司程序(session leader)。會話上司程序的PID成為識别會話的SID(session ID)。會話中的每個程序組稱為一個工作(job)。會話可以有一個程序組成為會話的前台工作(foreground),而其他的程序組是背景工作(background)。每個會話可以連接配接一個控制終端(control terminal)。當控制終端有輸入輸出時,都傳遞給該會話的前台程序組。由終端産生的信号,比如CTRL+Z, CTRL+\,會傳遞到前台程序組。 會話的意義在于将多個工作囊括在一個終端,并取其中的一個工作作為前台,來直接接收該終端的輸入輸出以及終端信号。 其他工作在背景運作。一個指令可以通過在末尾加上&方式讓它在背景運作。
man 7 signal 檢視信号 信号處理:無視、預設、自定義
每個程序會維護有如下6個ID:
真實身份: real UID, real GID 有效身份: effective UID, effective GID 存儲身份:saved UID, saved GID
其中,真實身份是我們登入使用的身份,有效身份是當該程序真正去操作檔案時所檢查的身份
為了防止其他的使用者身份被濫用,我們需要在操作之前,讓程序的有效身份變更回來成為真實身份。這樣,程序需要在兩個身份之間變化。
存儲身份就是真實身份之外的另一個身份。當我們将一個程式檔案執行成為程序的時候,該程式檔案的擁有者(owner)和擁有組(owner
group)可以被,存儲成為程序的存儲身份。在随後程序的運作過程中,程序就将可以選擇将真實身份或者存儲身份複制到有效身份,以擁有真實身份或者存儲身份的權限。并不是所有的程式檔案在執行的過程都設定存儲身份的。需要這麼做的程式檔案會在其九位(bit)權限的執行位的x改為s。這時,這一位(bit)叫做set
UID bit或者set GID bit。
$ls -l /usr/bin/uuidd -rwsr-sr-x 1 libuuid libuuid 17976 Mar 30 2012 /usr/sbin/uuidd
當我以root(UID), root(GID)的真實身份運作這個程式的時候,由于擁有者(owner)有s位的設定,是以saved
UID被設定成為libuuid,saved GID被設定成為libuuid。這樣,uuidd的程序就可以在兩個身份之間切換。
我們通常使用chmod來修改set-UID bit和set-GID bit:
$chmod 4700 file
我們看到,這裡的chmod後面不再隻是三位的數字。最前面一位用于處理set-UID bit/set-GID
bit,它可以被設定成為4/2/1以及或者上面數字的和。4表示為set UID bit, 2表示為set GID bit,1表示為sticky bit
(暫時不介紹)。必須要先有x位的基礎上,才能設定s位。
當一個程式調用fork的時候,實際上就是将上面的記憶體空間,包括text, global data, heap和stack,又複制出來一個,構成一個新的程序,并在核心中為改程序建立新的附加資訊 (比如新的PID,而PPID為原程序的PID)。此後,兩個程序分别地繼續運作下去。新的程序和原有程序有相同的運作狀态(相同的變量值,相同的instructions...)。我們隻能通過程序的附加資訊來區分兩者。 程式調用exec的時候,程序清空自身記憶體空間的text, global data, heap和stack,并根據新的程式檔案重建text, global data, heap和stack (此時heap和stack大小都為0),并開始運作。
多線程就是允許一個程序記憶體在多個控制權,以便讓多個函數同時處于激活狀态,進而讓多個函數的操作同時運作。即使是單CPU的計算機,也可以通過不停地在不同線程的指令間切換,進而造成多線程同時運作的效果。
一個棧,隻有最下方的幀可被讀寫。相應的,也隻有該幀對應的那個函數被激活,處于工作狀态。為了實作多線程,我們必須繞開棧的限制。為此,建立一個新的線程時,我們為這個線程建一個新的棧。每個棧對應一個線程。當某個棧執行到全部彈出時,對應線程完成任務,并收工。是以,多線程的程序在記憶體中有多個棧。多個棧之間以一定的空白區域隔開,以備棧的增長。每個線程可調用自己棧最下方的幀中的參數和變量,并與其它線程共享記憶體中的Text,heap和global
data區域。
最常見的解決競争條件的方法是将原先分離的兩個指令構成不可分隔的一個原子操作(atomic operation),而其它任務不能插入到原子操作中。
互斥鎖是一個特殊的變量,它有鎖上(lock)和打開(unlock)兩個狀态。互斥鎖一般被設定成全局變量。打開的互斥鎖可以由某個線程獲得。一旦獲得,這個互斥鎖會鎖上,此後隻有該線程有權打開。其它想要獲得互斥鎖的線程,會等待直到互斥鎖再次打開的時候。
/*mu is a global mutex*/ while (1) { /*infinite loop*/ mutex_lock(mu); /*aquire mutex and lock it, if cannot, wait until mutex is unblocked*/ if (i != 0) i = i - 1; else { printf("no more tickets"); exit(); } mutex_unlock(mu); /*release mutex, make it unblocked*/ }
條件變量特别适用于多個線程等待某個條件的發生。
/*mu: global mutex, cond: global codition variable, num: global int*/ mutex_lock(mu) num = num + 1; /*worker build the room*/ if (num <= 10) { /*worker is within the first 10 to finish*/ cond_wait(mu, cond); /*wait*/ printf("drink beer"); else if (num = 11) /*workder is the 11th to finish*/ cond_broadcast(mu, cond); /*inform the other 9 to wake up*/ mutex_unlock(mu); 共享讀取鎖(shared-read), 互斥寫入鎖(exclusive-write lock), 打開(unlock)。 管道(PIPE)機制。在中,我們提到可以使用管道将一個程序的輸出和另一個程序的輸入連接配接起來,進而利用檔案操作API來管理程序間通信。在shell中,我們經常利用管道将多個程序連接配接在一起,進而讓各個程序協作,實作複雜的功能。 傳統IPC (interprocess communication)。我們主要是指消息隊列(message queue),信号量(semaphore),共享記憶體(shared memory)。這些IPC的特點是允許多程序之間共享資源,這與多線程共享heap和global data相類似。由于多程序任務具有并發性 (每個程序包含一個程序,多個程序的話就有多個線程),是以在共享資源的時候也必須解決同步的問題 。 信号量semaphore與mutex類似,用于處理同步問題。我們說mutex像是一個隻能容納一個人的洗手間,那麼semaphore就像是一個能容納N個人的洗手間。其實從意義上來說,semaphore就是一個計數鎖(我覺得将semaphore翻譯成為信号量非常容易讓人混淆semaphore與signal),它允許被N個程序獲得。當有更多的程序嘗試獲得semaphore的時候,就必須等待有前面的程序釋放鎖。當N等于1的時候,semaphore與mutex實作的功能就完全相同。許多程式設計語言也使用semaphore處理多線程同步的問題。一個semaphore會一直存在在核心中,直到某個程序删除它。 共享記憶體與多線程共享global data和heap類似。一個程序可以将自己記憶體空間中的一部分拿出來,允許其它程序讀寫。當使用共享記憶體的時候,我們要注意同步的問題。我們可以使用semaphore同步,也可以在共享記憶體中建立mutex或其它的線程同步變量來同步。由于共享記憶體允許多個程序直接對同一個記憶體區域直接操作,是以它是效率最高的IPC方式。 消息隊列(message queue)與PIPE相類似。它也是建立一個隊列,先放入隊列的消息被最先取出。不同的是,消息隊列允許多個程序放入消息,也允許多個程序取出消息。每個消息可以帶有一個整數識别符(message_type)。你可以通過識别符對消息分類 (極端的情況是将每個消息設定一個不同的識别符)。某個程序從隊列中取出消息的時候,可以按照先進先出的順序取出,也可以隻取出符合某個識别符的消息(有多個這樣的消息時,同樣按照先進先出的順序取出)。消息隊列與PIPE的另一個不同在于它并不使用檔案API。最後,一個隊列不會自動消失,它會一直存在于核心中,直到某個程序删除該隊列。 共享記憶體(shared memory)。