7進程環境數據庫
7.2 內核執行C程序時,在調用main前先調用一個啓動例程。編譯器調用連接器將此例程指定爲程序起始地址,這個啓動例程從內核取得命令行參數和環境變量值,而後調用main。編程
7.3 進程終止的8種方式:安全
正常的5種:從main返回;調用exit;調用_exit和_Exit;最後一個線程從其啓動例程返回;最後一個線程調用pthread_exit;多線程
異常的3種:調用abort,接到一個信號終止,最後一個線程對取消請求作出迴應。 異步
exit函數會執行一系列的清理工做,而後纔會進入內核,而_exit和_Exit則會直接進入內核。例如當在程序中使用atexit系統調用註冊了清理函數,在使用exit退出時,註冊的函數將會按照順序依次執行;而使用_exit _Exit函數退出時,註冊的函數將不會執行。函數
7.6 C程序的存儲佈局空間工具
課後習題中有一個與本節相關的,即 爲何有些系統不容許程序訪問本身的低地址0,由此圖可得知。佈局
7.8存儲器分配的三個函數 malloc calloc(此函數分配空間後空間中每一位被初始化爲0),realloc。優化
這三個函數返回的指針必定是適當對齊的以使其用於任何數據對象。要知足最大的數據類型的對齊。好比存在int,char,double等數據時,指針的值要按照對齊要求最多的數據類型double來肯定爲8的倍數。ui
這三個alloc程序返回void*,若是程序中包含了#include<stdlib.h>就無需再進行強制類型轉換。
這三個函數使用sbrk調用實現,sbrk能夠擴充或縮小進程的存儲空間,可是大多數malloc和free都不減少進程空間,一般將free掉的內存繼續放在進程的malloc池中而不返回給內核。
大多數malloc分配的空間要比要求的更大一點,由於要記錄管理信息,該塊的長度,下一個塊地址等等。
7.10 setjmp和longjmp與goto函數的區別,前者跨越函數(在多個函數間)進行跳轉,而goto只能在一個函數中進行跳轉。
當調用跳轉時,自動、寄存器變量和易失變量的變化較爲複雜,書中例子以下
static int globval;
1: int main()
2: {
3: int autoval;4: register int regival;5: volatile int volaval;6: static int statval;7:
8: globval=1;autoval=2;regival=3;volaval=4;statval=5;
9:
10: if(setjmp(jmpbuffer) != 0)11: {
12: printf("after longjmp:\n");13: printf("globval = %d,autoval = %d,regival = %d,volaval = %d,statval = %d\n14: globval,autoval,regival,volaval,statval");
15: exit(0);
16: }
17:
18: globval=95;autoval=96;regival=97;volaval=98;statval=99;
19: printf("before longjmp:\n");20: printf("globval = %d,autoval = %d,regival = %d,volaval = %d,statval = %d\n21: globval,autoval,regival,volaval,statval");
22: longjmp(jmpbuffer);
23: }
運行結果:
gcc testjmp.c 而後運行,兩次輸出結果相同,就是說跳轉之後,程序從內存中從新讀取了數據;
若是gcc –O testjmp.c 而後運行,靜態變量和vovatile變量數據爲90+的數據,而自動和局部變量都是跳轉之前的值。由於進行了優化編譯後,編譯器會優先採用寄存器中的值而不是讀取存儲器,因此咱們未加vovatile修飾的局部變量就會讀取寄存器中的值。而longjmp跳轉後,寄存器中的值會被恢復爲setjmp時候的值,因此咱們打印出的自動和局部變量是原來的小數字,而靜態和vovatile數據會從新讀取存儲器得到,就是大數字。
關於自動變量還有一個潛在問題,就是聲明自動變量的函數返回後,該自動變量因爲退棧就已經不存在了,在這以後不能引用該自動變量。
8進程控制
8.2fork函數
由fork建立子進程,子進程是父進程的副本,得到父進程數據空間、堆和棧的副本,父子進程共享正文段。因爲fork後常常跟隨者exec調用,因此如今不少實現是這樣的,在fork後並不複製父進程的數據段、堆棧;採用寫時複製,即這些區域由父子共享,且這些區域爲只讀;當父子進程有一個試圖改變這些區域,內核將爲修改區域那塊內存製做一個副本。父子進程共享同一張文件表,這意味着父子進程對同一個文件共享文件偏移量,父進程更改了文件偏移量,子進程也會更新。
8.4vfork函數
vfork建立一個子進程,子進程會立刻調用exec或exit,因此vfork出來的子進程並不複製父進程的地址空間,在執行exec和exit以前,它在父進程地址空間中運行。
8.5
僵死進程:一個已經終止可是其父進程並未對其進行善後處理(獲取終止子進程的有關信息,釋放其佔用資源)的進程。
父進程如何獲取子進程狀態:內核爲每一個終止子進程保存必定信息,父進程調用wait或者waitpid時就能夠獲得這些信息。
若是子進程還未結束,父進程就已經掛掉,那麼這些孤兒進程的父進程被設置爲init進程。init進程永遠不會結束,那麼在init收養的某個進程結束後,init就會調用一個wait函數來取得其終止狀態,並進行處理,防止這些進程成爲僵死進程。
8.11更改用戶ID和組ID
在UNIX系統中,特權是基於用戶和組ID的,當程序須要增長特權,或須要訪問當前並不容許訪問的資源時,咱們須要更換本身的用戶ID或者組ID,使得新ID具備合適的特權或訪問權限。同時,當程序須要下降其特權或阻止對某些資源的訪問時,也須要更換用戶ID和組ID。
改變用戶ID和組ID有兩種途徑:1,exec執行程序時,根據程序文件的設置用戶ID位是否打開,來更改,有效用戶ID位爲程序文件擁有者,實際用戶ID爲運行此程序的用戶。若是設置用戶ID位未打開,則不能改變。
2,setuid函數,當運行程序的進程有超級用戶權限時候,setuid(uid)將實際用戶ID有效用戶ID保存的設置用戶ID設置爲uid;運行程序沒有超級用戶權限,可是uid=實際用戶ID或者設置用戶ID,那麼僵有效用戶ID設置爲uid;若是不是上面兩種狀況,那麼setuid則會設置errno返回。
書中使用man程序對這個過程進行解釋。
10 信號
10.2 處理信號的三種方式:1,忽略;2,捕捉;3執行系統默認動做。
兩個常量的意義:SIG_IGN表明內核忽略信號,SIG_DFL表示採用系統默認動做。
10.5 中斷的系統調用
若是進程在執行一個低速系統調用而阻塞期間捕捉到一個信號,則該系統調用就被中斷再也不繼續執行。返回出錯值,將errno設置爲EINTR。
10.6可重入函數
進程捕捉到信號並對其進行處理時,進程正在執行的指令序列就被信號處理程序中斷,執行完信號處理程序後,返回到中斷處再繼續執行被中斷的指令序列。可是有些函數被中斷後返回可能會形成問題,這些函數就是不可重入的,相反中斷後返回繼續執行無問題的函數即稱爲可重入的函數。
可重入函數主要用於多任務環境中,一個可重入的函數簡單來講就是能夠被中斷的函數,也就是說,能夠在這個函數執行的任什麼時候刻中斷它,轉入OS調度下去執行另一段代碼,而返回控制時不會出現什麼錯誤;而不可重入的函數因爲使用了一些系統資源,好比全局變量區,中斷向量表等,因此它若是被中斷的話,可能會出現問題,這類函數是不能運行在多任務環境下的。
10.11信號相關的一些函數和結構
sigset_t 信號集;sigprocmask(int,const sigset_t *,sigset_t *)更改信號屏蔽字的函數;sigaction(int,const struct sigaction*,struct sigaction* ),修改與指定與信號相關聯的處理動做;sigsuspend,pause,sigwait這幾個函數可用於實現等待某個信號;
11線程
11.2線程概念
線程的一些好處:
經過爲每種事件類型的處理分配單獨的線程,可以簡化處理異步事件的代碼;
多個進程必須使用操做系統提供的複雜機制才能實現內存和文件描述符的共享,多個線程自動地能夠訪問相同的存儲地址空間和文件描述符;
多線程能夠提高程序的並行性;
11.5線程終止
終止的三種方式:1,調用pthread_exit;2從啓動例程中返回;3被同一進程中其餘線程取消(經過調用pthread_cancle);
前兩種方式退出後,進程中其餘線程能夠經過調用pthread_join函數得到該線程的退出狀態。
線程能夠安排本身退出時須要調用的函數來處理後事,相似於進程的atexit註冊函數,線程也有本身的函數pthread_cleanup_push和pthread_cleanup_pop。
11.6線程同步
多個線程共享相同的內存時,須要確保每一個線程看到一致的視圖。若是共享的變量是隻讀的則對此變量的訪問無需同步,可是當變量非只讀時就必須考慮同步。
互斥量,讀寫鎖,條件變量是經常使用的實現同步的工具。
互斥量即pthread_mutex_t數據類型,經過使用pthread_mutex_init初始化,pthread_mutex_lock和pthread_mutex_unlock來加解鎖。要避免死鎖問題,同一個執行流中對一個互斥量連續加鎖兩次會死鎖,兩個線程使用兩個互斥量的狀況下要確保線程加鎖順序相同不然也會死鎖。
讀寫鎖容許更高的並行,一次只有一個線程能夠佔有寫模式的讀寫鎖,可是多個線程能夠同時佔有讀模式的讀寫鎖。
條件變量與互斥量一塊兒使用時,容許線程以無競爭的方式等待特定條件的發生。
12線程控制
12.5重入
若是一個函數在同一時刻能夠被多個線程安全調用,就稱該函數式線程安全的。若是一個函數對多個線程來講是可重入的,則說這個函數是線程安全的,但這並不可以說明對信號處理程序來講該函數也是能夠重入的。若是函數對信號處理程序的重入是安全的,那麼就說函數是異步-信號安全的。
12.6 線程私有數據
errno是線程私有化數據。
12.8線程和信號
每一個線程都有本身的信號屏蔽字,可是信號的處理程序是進程中全部線程共享的。
進程中的信號時遞送到單個線程去的,若是信號與硬件故障或者計時器超時有關,該信號就被髮送到引發事件的線程去,而其餘信號則被髮送到任意一個線程。
線程能夠經過調用sigwait等待一個或者多個信號的發生。使用sigwait的好處是能夠簡化信號處理,爲了防止信號中斷線程,把信號加入到每一個線程的信號屏蔽字中,而後安排專用線程作信號處理,這些專用線程能夠進行函數調用,不須要擔憂在信號處理程序中調用哪些函數是安全的,由於這些函數調用來自正常線程環境。
12.9線程和fork
在子進程內部只存在一個線程,它是由父進程中調用fork的線程的副本構成的。
14高級I/O
非阻塞I/O,對一個給定的描述符有兩種方法對其指定非阻塞I/O,1,若是調用open得到描述符,能夠指定O_NOBLOCK標誌;2,對於已經打開的描述符,能夠調用fcntl打開O_NOBLOCK文件狀態標誌。
記錄鎖:當一個進程在讀或者修改文件的某個部分時,它能夠阻止其餘進程修改同一文件區。
對早期的UNIX系統的一種批評是它們不能用來運行數據庫系統,由於這些系統不支持部分的對文件加鎖。如今能夠經過使用fcntl函數來實現記錄鎖功能。
int fcntl(int filedes,int cmd,…)。對於記錄鎖,cmd是F_GETLK、F_SETLK、F_SETLKW。第三個參數是一個紙箱flock結構的指針。
I/O多路轉接:當從一個描述符讀,而後又寫到另外一個描述符時,能夠在下列形式的循環中使用阻塞I/O:
1: while((n = read(STDIN_FILENO,buf,BUFSIZ)) > 0)2: if(write(STDOUT_FILENO,buf,n) != n)3: err_sys("write error");
這種形式的I/O很常見,可是若是必須從兩個描述符讀,若是使用阻塞I/O,那麼可能長時間阻塞在一個描述符上。select和poll函數能夠用來面對這個問題。
select等待設定的描述符集中哪一個可用,當有一個可用時select就返回。
select在Socket編程中仍是比較重要的,但是對於初學Socket的人來講都不太愛用Select寫程序,他們只是習慣寫諸如connect、accept、recv或recvfrom這樣的阻塞程序(所謂阻塞方式block,顧名思義,就是進程或是線程執行到這些函數時必須等待某個事件的發生,若是事件沒有發生,進程或線程就被阻塞,函數不能當即返回)。但是使用Select就能夠完成非阻塞(所謂非阻塞方式non-block,就是進程或線程執行此函數時沒必要非要等待事件的發生,一旦執行確定返回,以返回值的不一樣來反映函數的執行狀況,若是事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,因此效率較高)方式工做的程序,它可以監視咱們需要監視的文件描述符的變化狀況——讀寫或是異常。