1 "-----第六天-----------------------------------------------------------------------------" 2 3 1.版本控制:svn/git; 4 5 2.進程的概念: 6 1)程序和進程; 7 每個進程操作系統會為它分配 0-4G 的虛擬內存空間(32位操作系統); 其中0-3G為用戶內存空間,進程可以對它進行讀寫操作; 3G - 4G 為系統內核空間,進程沒有讀寫權限。 8 進程只能讀寫用戶空間,沒有權限讀寫內核空間(kernel); 9 2)內存頁面的概念 10 操作系統是按頁來管理內存的;每個頁有4096個字節。 11 還可以設置頁面屬性:如char *str = "hello",字符串在只讀數據段,此時只能讀,不能寫。 12 13 3)進程的4種形態:0-3級; 0 級(內核態),最高; 3級(用戶態);最低。 14 15 2)並發: 16 3)單道程序設計/多道程序設計 17 4)CPU/MMU 18 5)PCB:結構體;其中有一個指針指向文件描述符表;文件描述符表中存儲1024個指針;已經打開的文件結構體。 19 每個進程有屬於自己的PCB(進程的狀態描述符);但是多個進程只有一個內核區。 20 3.環境變量控制 21 1)echo $名稱; 查看該名稱的環境變量。 22 2)查看系統的環境變量: env; 23 3)獲取環境變量的值:char *getenv(const char* name); 獲取鍵name環境變量的值。 24 4)設置環境變量:int setenv(const char *name, const char *value, int rewrite); 將環境變量name的值設置為 value。 25 如果環境變量 name 已存在;則 rewrite 非0,覆蓋原來的環境變量;rewrite為0,則不覆蓋原來的環境變量。 26 5)刪除環境變量: void unsetenv(const char *name) 刪除 name 的定義; 27 28 6)進程里可以設置自己的環境變量,避免設置在終端,影響他人。 29 4.環境控制原語: 30 1)fork: 31 2)wait/waitpid 32 3) 33 34 5. printenv.c 35 36 6.進程的狀態:4 種或 5 種,都對。 就緒, 運行, 睡眠, 停止。 37 38 7.CPU的組成狀態: 運算器,寄存器,控制器,譯碼器。 39 40 操作系統完成進程調度。(cpu進行時鍾周期運算) 41 cpu的分時復用功能,使進程看起來像多進程。 42 在單核 cpu 上,同一時間只能有一個進程處於運行狀態。那么多核 cpu,就可以有多個進程處於運行狀態。 43 44 8.進程原語: 45 1)創建子進程。pid_t fork(); 調用1次,返回兩次;在父進程返回子進程的 PID, 在子進程返回0. 46 2) fork()的工作進程:先調用 creat(),創建一個進程, 在調用 clone(),給子進程復制父進程的內容; 47 3)pid = fork(); //此時父子進程就都出來了。 48 4)pid_t getpid(); 返回調用進程的PID號; 49 pid_t getppid(); 返回調用進程的父進程的PID號 50 當在子進程中 getpid(),得到的值與 fork()父進程的返回值相等,都是這個子進程的id號 51 5)父子進程:讀時共享,寫時復制。 52 6)最大創建進程 個數; 53 9. uid_t getuid(void); //返回實際用戶id; 54 uid_t geteuid(void); //返回有效用戶id; 55 "注意: 文件實際用戶與有效用戶在創建該文件的家目錄下時,如果沒有修改,此時是相通的。 56 當文件被拷貝到root目錄時, " 57 設置用戶id; chmod 04755; 4 是設置用戶ID。 完后效果為 -rwsr-xr-x; s表示設置了用戶ID。 58 作用:當執行此文件時,執行者有效用戶ID變為此文件的所有者。 59 設置用戶組ID: chmod 06777; 6 是設置用戶ID和組ID; 效果為: -rwsrwsrwx; 60 61 10. exec族函數的使用: 62 1)功能: 去磁盤中加載另一個可執行程序,用它的代碼段、數據段替換掉當前可執行程序的代碼段、數據段;然后從后加載的這個程序退出,被替換的程序后續代碼就不執行了。(換核不換殼,) 63 2)一般情況下:與 fork() 函數聯合使用;。 同時exec族函數不會創建新進程, 所以該程序的 ID 不會改變。 64 65 3)函數的返回值: 成功沒有返回值,運行完畢,自己退出。 66 失敗返回 -1; 67 4)exec族函數的規律: l(list):命令行參數列表。 p(path): 搜索file時使用path變量。 68 v(vector):使用命令行參數數組。 e(environment):使用環境變量的數組,不使用進程原有的環境變量,設置新加載程序的環境變量; 69 70 5)int execl(const char *path, const char *argc, ...); 71 例如:execl("./bin/ls", "ls", "-l", "-a", NULL); 這幾個參數的作用:其中第一個參數:"./bin/ls",命令的環境變量(即所在文件),不可以改變,必須完整正確; 72 "ls":占位參數,可以隨便寫,一般寫成該命令。(不能缺失,如果缺失了,就會把后面的命令參數變為占位參數,輸出結果會出錯。) "-l","-a"該命令的參數。 NULL:衛兵,執行到這里時命令結束。 73 74 11. toupper():將小寫字母變為大寫。 75 76 12.僵屍進程,孤兒進程: 77 1)S:睡眠狀態; Z:僵屍狀態; 78 2)僵屍進程的產生原因:用戶空間釋放,內核空間的PCB沒有釋放,等着父進程回收。(它消耗的是內核當中的內存資源) 79 即:子進程退出,父進程沒有回收子進程資源(PCB),則子進程變為僵屍進程。 80 3)子進程的PCB(在內核空間)沒有釋放,是留給父進程回收用的。只有父進程回收后,僵屍進程才會消失。 81 4)殺死僵屍進程的辦法是殺掉它的父進程。 82 83 孤兒進程:父進程先於子進程結束,則子進程變為孤兒進程,子進程的變為1號進程init進程,由1號進程領養。 84 85 "僵屍進程比孤兒進程更危險, 因為它不會自動關;,而孤兒進程會由 1 號進程領養,當它執行完畢后,會被 1 號進程回收"。 86 做開發時主要避免的是僵屍進程的產生。用 wait(); waitpid(); 來避免僵屍進程的產生。 87 88 89 13. pid_t wait(int* status); (阻塞函數,等待回收子進程資源;如果沒有子進程,返回-1); 90 1)返回值:回收的子進程的ID號, wait(NULL);不關心子進程如何死亡,直接回收。 91 wait(&st); 用st來保存子進程死亡時的狀態。 92 93 2) 父進程的ID與它的進程組ID相同;子進程的組ID與父進程的ID相同。 94 kill -9 -父進程的ID(即組進程的ID),這個進程組的所有進程都被殺死。"(注意 - 不能少)" 95 96 14.pid_t waitpid(pid_t pid, int *status, int options); (設置非阻塞。) 97 第一個參數 pid的值類型: 1) < -1; 回收指定進程組內的任意子進程。(因為父進程與子進程屬於統一進程組,父進程與孫子進程屬於不同的進程組,但是他們有血緣關系。) 98 -1;回收任意子進程(只要有血緣關系就行。) 99 0; 回收 當前調用 waitpid 一個進程組的所有子進程。 100 > 0; 回收指定ID的子進程。 101 第二個參數:保存子進程的推出狀態。 102 103 第三個參數:WNOHANG:如果沒有子進程退出,立即返回。(實現了非阻塞的 wait). 104 105 15. 阻塞函數:非阻塞函數:在文件open(O_NONBLOCK)的時設置宏;讀常規文件不會出現阻塞,當讀偽文件時會出現。 106 例如:阻塞讀終端; 管道; 網絡; 107 108 設置非阻塞后:應設置輪詢。 109 110 111 "=========進程間的通信(IPC)===========================================================================================================" 112 一。IPC方法:Linux環境下,多個進程間的通信,需要通過內核,在內核中開辟一塊緩沖區(賦予他用戶權限);進程1把數據從用戶空間拷貝到內核緩沖區, 113 進程2 把數據從內核緩沖區拷讀走,內核提供的這種機制稱為進程間的通信(IPC)。 114 115 進程間的通信;四種方法:1.pipe管道 2.fifo有名管道,3.內存共享映射 4.Unix Domain Socket; 116 管道(使用最簡單);信號(開銷最小);共享映射區(速度最快,效率最高); 本地套接字(最穩定); 117 118 二。pipe管道: 119 1.管道的特性:數據只能一個讀,一個寫,必須是一個方向。 120 半雙工:數據同一時刻只能有一個流向。即只能父進程寫,子進程讀; 或子進程寫, 父進程讀。 121 全雙工:數據同一時刻可以兩個方向。 122 單工:數據只能同一個方向流動。 123 dup2(int oldfd, int newfd); 將第一個參數拷貝給第二個參數。 124 1.其本質是一個偽文件(實為內核緩沖區) 125 2.有兩個文件描述符引用,一個讀端,一個寫端。 126 3.規定數據從管道的寫端流入,從讀端流出。 127 128 管道的原理:管道實為內核使用環形隊列機制,借助內核緩沖區(4K)實現的。 129 130 管道的局限性:1.數據不能自己寫,自己讀; 131 2.管道中的數據不能反復讀取,一旦讀走,管道中不再存在。 132 3.采用半雙工通信方式,數據只能在單方向上流動。 133 4.只能在有血緣關系之間的進程使用管道。 134 5.只能進行單向通信,雙向通信需要建立兩個管道。 135 136 2. pipe()創建管道,用於有血緣關系之間的通信。(采用環形隊列實現的) 137 管道使用半雙工通信; 創建完管道完后,確定通信方向:父寫子讀,或子寫父讀。 138 如果想創建多條管道,一定要先pipe(),再fork(),使子進程得以繼承管道。 139 140 使用管道時的四種注意情況: 141 1) 寫段關閉,讀端讀完管道里的內容時;再次讀,返回0,相當於讀到文件末尾EOF; 142 2)寫端未關閉,寫端無數據, 讀端讀完管道里的數據時,再次讀,阻塞。 143 3)讀端關閉, 寫段寫管道,產生SIGPIPE信號,寫進程默認情況下會終止進程。 144 4)讀端未讀管道數據,當寫段寫滿管道后,在此寫,阻塞。 145 5)使用管道,無須open,但需手動close。 146 147 管道的緩沖區大小:1.函數的方法:fpathconf(int fd, int name); 第一個參數為管道描述符;第二個參數為情況標識符。 148 2.命令: ulimit -a; 149 150 3. 總結: 151 1.讀管道: 152 1.管道中有數據,read返回實際讀到的字節數; 153 2.管道中無數據,寫端都被關閉,read返回 0,相當於讀到文件末尾; 154 寫端未關閉,read函數阻塞等待,(期待不久的將來會有數據到來,但此時會讓出CPU資源) 155 2.寫管道: 156 1.管道讀端全部關閉;進程異常中止(返回一個終止信號); 157 2.管道讀端未關閉: 158 1)管道已寫滿,write阻塞等待 159 160 2)管道未寫滿,write將數據寫入,返回實際寫入的字節數; 161 162 163 "===========================================================================" 164 4.設置非阻塞管道的兩種辦法: 165 1)fcntl函數設置非阻塞管道; 166 int flg = fcntl(fd, F_GETFD); 167 flg |= O_NONBLOCK; 168 fcntl(fd, F_SETFL, flg); 169 2)打開文件時直接設置非阻塞; 170 int fd = open("/dev/tty", O_RDWR | O_NONBLOCK); 171 當讀取一個非阻塞文件,但是此時沒有內容,會出錯,錯誤碼為EAGAIN; 172 5. read() 函數的返回值四種情況;由於非阻塞設置的存在. 173 1.返回值 > 0; 讀到的字節數 174 2.返回值 < 0; 讀到文件末尾 175 3.返回值 == -1;但是errno != EAGAIN 或 EWOULDBLOCK 176 if( ret == -1) { 177 if(errno == EAGAIN) 178 { 179 說明文件被設置為非阻塞方式讀取,此時數據沒有到達。 180 } 181 else 182 { 183 失敗; 184 } 185 } 186 4.返回值 == -1, 但是errno == EAGAIN 或 EWOULDBLOCK; 187 說明此時文件被設置為非阻塞方式讀取,數據還沒有到達。 188 "========================================================================" 189 三. fifo有名管道:解決無血緣關系的進程通信; 190 1.介紹: 191 FIFO文件在磁盤上沒有數據塊,僅僅用來標識內核中的一條管道。各進程可以打開這個文件進行read/write,實際上是在寫內核通道,這樣就實現了進程間的通信。 192 1.創建fifo的方法: 193 1)在終端創建一個有名管道;(不常用)用命令;"mkfifo 管道名"; 194 2)在代碼運行時創建一個有名管道;(常用)用函數;int mkfifo(const char*pathname, mode_t mode);成功返回 0,失敗-1; 195 196 2.利用fifo實現非血緣關系進程間通信 197 1.進程使用 “同一個fifo” 完成進程間通信。 198 2.一個進程以只讀方式打開read端,一個程序以只寫打開寫段。 199 3.一根管道可以打開多個讀端,多個寫段。(一個寫段多個讀端; 或 一個讀端多個寫段); 200 201 3.多種特殊情況: 202 當一個寫端多個讀端時: 每個讀端讀取到的數據都不相同,因為從管道中讀數據,讀走之后,管道中的這個數據就不存在了;此時每個讀端讀到的所有內容合在一起為 203 管道寫端寫入的數據 204 當多個寫端一個讀端時, 每個寫端寫入的數據都會被這個讀端讀取出來;此時管道讀到的數據為所有寫端寫入的數據。 205 206 四。mmap 內存映射: 207 208 從磁盤映射到kernel區,同時kernel區這段內存(映射占用的)開放了用戶區的權限;所以進程可以進行訪問 209 210 1.文件是應用於進程間通信 211 1)父子進程之間 212 可以用文件進行通信; 213 2)非血緣關系進程間通信 214 可以用文件進行通信; 215 216 2.建立映射區,完成進程間通信 217 1)mmap函數;六個參數的不同意義 218 219 2)注意事項 220 1.open的時候可以創建一個新文件,但是用此文件創建映射區時,大小不能為0;(可以進行文件拓展后再用來創建映射) 221 mmap使用的時候經常出現總線錯誤,通常是由於共享文件存儲空間大小引起的。 222 223 2.創建映射區的時候, 隱含着一次對文件的讀操作;所以文件打開的方式只能為"讀寫;或讀"。 224 當 MAP_SHARED(對內存的修改將反應到磁盤空間) 時,要求 映射區的權限 <= 文件打開的權限。 225 當 MAP_PRIVATE(對內存修改不會反應到磁盤空間) 時,此時則沒有要求。(mmap中的權限是對內存的限制); 226 227 3.映射區創建完后關閉文件按描述符,對映射區沒有影響。 228 4."最后一個參數,偏移量必須是4K的整數倍。" 229 5.munmap函數傳入的地址一定要是mmap的返回地址。堅決杜絕對mmap函數的返回值進行++操作 230 6.mmap創建映射區出錯的概率非常高,必須進行出錯判斷。 231 232 3)父子進程間的mmap(用戶空間獨立,內核去各進程共享。) 233 1.先mmap創建映射區 234 2.再fork()共享映射區內存首地址 235 3.指定MAP_SHARED;(必須是 SHARED); 236 237 4)非血緣關系進程間通信 238 1.使用“同一個文件”創建映射區 239 2.兩個進程,一個以read映射區,一個以write方式打開映射區; 240 3.指定 MAP_SHARED; 241 4)MAP_ANONYMOUS(匿名映射); 不可以用來通信 242 5)/dev/zero(系統的偽文件); 不可以用來通信 243 5)匿名映射 244 linux 創建匿名映射:第四個參數添加 MAP_ANON或MAP__ANONYMOUS;(注意這個匿名宏,只有Linux操作系統可以使用)。 245 246 UNIX(沒有上述的匿名宏)。使用系統中的特殊文件(/dev/zero;(無限讀,可以用來創建匿名映射)) 247 248 3.數據可以重復讀取;(即多個讀端讀取一個寫端,讀取的內容相同,與fifo不同,取決於讀取的數據)。 249 250 251 252 "====================進程間的通信(IPC)================================================================================================"
