linux C中的多進程概念與log日誌與部分python多進程

1.以前學習linux C語言時整理的關於進程的概念,這裏先記錄下

一、一、程序的開始和結束

一、main函數由誰調用

(1)咱們以前在寫裸機代碼的時候,須要有段引導代碼start.S
(2)咱們操做系統中的應用程序,也是須要一段引導代碼的,在咱們編寫好一個應用程序的時候,咱們連接這個應用程序的時候,連接器會從編譯器中將那段引導代碼加上連接進去和咱們的應用程序一塊兒生成可執行程序,用gcc -v xxx.c編譯一個程序的時候咱們能夠看到這些詳細的信息,包括預處理,編譯,連接,gcc內部有一個默認的連接腳本,因此咱們能夠不用用連接腳本進行指定。
(3)運行時的加載器,加載器是操做系統的一個代碼,當咱們執行一個程序的時候,加載器會將這段程序加載到內存中去執行程序編譯時用編譯器,程序運行時用加載器,由加載器將程序弄到內存中運行。
python

二、argc,和argv

(1)當咱們執行一個程序的時候,帶了參數的話,由於我執行這個程序的時候,其實所執行的這個程序也至關於一個參數,所在的環境實際上是一個shell的環境,shell這個環境接收到了這個兩個參數後會給加載器,加載器找到了main函數,而後將這個兩個參數給了main,
linux

三、程序如何結束

(1)程序正常終止:return、exit、_exit,就是程序有目的終止了,知道何時終止
(2)程序非正常終止:本身或他人發信號終止進程
shell

四、atexit註冊進程終止處理函數

(1)atexit函數註冊的那個函數,是在程序正常終止的時候,會被執行的,也就是說,若是一個程序正常終止是執行到return 0,表示這個程序正常終止了,若是你使用的atexit函數的後面還有函數,執行順序不是先執行atexit函數,在執行後面的那個函數,而是先執行後面的函數,在執行atexit函數,就是說,程序正常終止時,這個atexit註冊的那個函數纔會被執行編程

(2)當一個進程中屢次用atexit函數註冊進程終止處理函數時,在程序正常終止的時候,先註冊的會被後執行,後註冊的會先執行,由於你先註冊了,說明你這個進程正常終止的時候,你這個先註冊的會有優先起到做用,跟棧的先進後出同樣
(3)return exit _exir 的區別:return 和 exit 來讓程序正常終止的時候,atexit註冊的進程終止處理函數會被執行,可是_exit終止時就不會執行進程終止處理函數
windows

一、二、進程環境

1.環境變量

(1)咱們在命令行下,能夠用export看到全部的環境變量
(2)進程所在的環境中,也有環境變量,進程環境表將這些環境變量構成了一個表,當前的進程可使用這個當前進程環境表的環境變量
(3)進程中經過environ全局變量使用當前進程的環境變量,這個全局變量是由庫提供的,只用聲明,不用定義的。
environ這個全局變量聲明的方法,extern char environ,由於當前進程可使用的環境變量,是在進程環境表中的,是一個一個的字符串,因此用了二重指針,是一個字符串數組,指針數組,數組中的每一個元素都指向一個字符串,進程環境表實際上是一個字符串數組,用environ變量指向這個表來使用,因此可使用environ[i]來遍歷全部的環境變量
數組

(4)獲取環境變量,getenv函數
getenv函數,C庫函數,傳一個環境變量的名字,返回一個當前環境變量的值,都是字符串格式的,獲取或者改環境變量,只是獲取和修改的你當前進程中的環境變量
瀏覽器

一、三、進程運行的虛擬地址空間

**(1)操做系統中的每個進程都覺得本身是操做系統中的惟一的一個進程,因此都認爲本身享有獨立的地址空間,可是實際上不是這樣的,每個進程至關於分時複用的。br/>(2)操做系統爲每個進程分配了邏輯空間4GB(32位系統),每一個進程都天真的覺得,有4G的內存,其中有1G是給操做系統的,其他的都是給本身用的,但實際上可能整個電腦就只有512MB的物理內存,操做系統可能只須要用到10M內存,這10M內存就真正的映射到了物理內存上,剩下的1G-10M的內存是空的,至關因而虛假的,當你用到了的時候,在將你用到的相應的內存映射到物理內存上,不用的時候,就又不映射到物理內存上。因此雖然你至關於有4GB的邏輯虛擬內存,可是你的物理內存真正並無那麼多,而是你邏輯虛擬內存用一點,對應你就去物理內存中用一點,但你確定不會一會兒用的內存多於物理內存,當你不用時,你要把內存還回去了,因此這樣的狀況就至關於你的程序能夠本身認爲本身在4G內存中跑着的,程序用了多少內存都會去物理內存中取一點用,用完後又還回去了,因此這4G內存是虛擬出來的。雖然進程認爲本身有4G的內存,可是你的進程可能只會用到4M內存,因此就對應物理內存中的4M,只是欺騙了進程你有4G內存。
因此也就是說每個進程是工做在4G的邏輯虛擬內存中的,可是你的進程並不可能用到這麼真正的對應的物理內存,你在邏輯虛擬內存中用到了1M的內存,就給你分配物理內存中的1M進行對應,用10M就給你10M,只是你本身覺得你有4G的內存。
這樣作的意義:
@1:爲了讓進程隔離,爲了安全,讓進程之間看不到對方。讓每一個進程都覺得本身獨立的生活在那4G的內存空間上。若是進程不隔離的話,好比你在用支付寶,這個進程運行着,你又運行了QQ,這個進程也運行着,可是由於進程不隔離,QQ進程能看支付寶進程,那麼QQ進程就會用一些手段能獲取到你的支付寶密碼了。
@2:提供多進程同時運行,由於每一個程序在運行的時候,確定都須要連接到真正的物理地址內存上去運行的,可是你的進程那麼多,你的每一個進程都是獨立的,那麼就覺得着每一個進程都是從0地址這個虛擬地址開始的,那怎麼將不一樣的應用程序連接到不一樣的物理地址內存中去,正由於有了這個虛擬地址和物理地址的映射關係,操做系統內部對內存這塊作了不少的管理,咱們就不須要操心了,每個進程運行的時候,操做系統都會爲這個進程分配一個能夠連接運行的物理地址,不會干擾到別人的**安全

一、四、什麼是進程

一、進程是一個動態過程而不是實物

(1)進程就是程序的一次運行過程,一個程序a.out被運行後,在內存中運行,重運行開始到結束就是一個進程的開始和結束,a.out運行一次就是進程,運行一次就是一個進程服務器

二、進程控制塊PCB(proess control block)

(1)內核中專門用來管理進程的一個數據結構,就叫進程控制塊。操做系統用一個進程控制塊,來管理和記錄這個進程的信息網絡

三、進程ID

**(1)一個程序被運行了,就是一個進程,每個進程操做系統都會其分配一個ID,就是一個號碼,來惟一的標識這個進程,進程ID是進程控制塊這個結構體中的一個元素。
(2)在Linux的命令行下,能夠用ps命令來打印當前的進程(當前在運行的程序),就有當前進程的ID。 ps -a 能夠查看全部的進程 ps -aux 查看操做系統全部的進程(這多是包括以前運行的進程和當前運行的進程)
(3)在編程的過程當中,也能夠用函數來得到進程號
getpid、獲取當前進程的本身的PID,進程ID號
getppid、 獲取父進程的ID號
getuid 獲取當前進程的用戶ID
geteuid、 獲取當前進程的有效用戶ID
getgid、 獲取當前進程組的ID
getegid 獲取當前進程的有效組ID

實際用戶ID,有效用戶ID,實際組ID,有效組ID(有須要去百度吧)**

四、多進程調度的原理

(1)操做系統同時運行多個進程
(2)宏觀上的並行,微觀上的串行
(3)實際上現代操做系統最小的調度單元是線程而不是進程

五、fork建立子進程,fork是一個系統調用

(1)每一次程序的運行都須要一個進程,操做系統運行你的程序的時候是須要代價的,代價就是要讓這個程序加載到一個進程中去運行,這個進程又不是憑空出現的,是須要建立的,因此操做系統運行一個程序的時候,是要先構建一個進程的,就構建了一個進程的進程控制塊PCB,這是一個結構體,既然你要構建,你就是一個結構體變量,是須要內存的,這個進程控制塊中,就有這個進程的ID號,就須要將你的程序用這個進程控制塊PCB來進行管理和操做。
也就是說,你的程序想要在操做系統中運行的話,就必定要參與進程調度,因此就必定要先構建一個進程,因此先構建一個進程控制快PCB,將這個程序用這個進程控制塊來管理,這個進程控制塊就是一個結構體變量,裏面有好多好多的元素,就至關於一個房子,一個讓程序住的房子,把這個程序當成了一個進程來管理,進程控制塊中就有這個進程的好多信息,好比PID號等等。
(2)linux 中製造進程的方法就是拿老進程來複製出來一個新進程
(3)fork以前的的代碼段只有父進程擁有,fork以後的代碼段,父子進程都有,數據段,棧這些內存中的數據,即便在fork以前父子進程也都有,由於fork的時候是將父進程的進程控制塊中的代碼段複製到子進程的進程控制塊中,而全局變量,棧,數據值都是擁有的

六、fork內部原理

(1)進程的分裂生長模式:若是操做系統須要運行一個程序,就須要一個進程,讓這個程序在這個進程控制塊中。作法是,用一個現有的老的進程複製出一個新的進程(好比用memcopy直接將一個老的進程控制塊進行復制,由於是結構體變量嘛),而後在這個複製過來的基礎上去修改,好比修改這個進程塊中描述當前運行的那個程序的內容,給改爲咱們這個程序,讓咱們這個程序的代碼段到這個位置。既然是複製的,因此就有了父進程和子進程

總結:一個程序想要在操做系統上運行的話,操做系統是須要先構建一個進程的,爲了能其進行調度。構建了一個進程控制塊PCB,這是一個結構體,用這個結構體定義了一個變量,讓這個變量中進行填充內容,這就是一個進程的樣子,裏面放了這個要執行的程序的代碼段等等,還未這個程序弄了一個進程ID號,不過,在構建一個進程的時候,不是直接從頭開始構建的,而是用老的進程複製了一個新的進程,就是用memcopy將老進程也就那個結構體變量中的內存空間複製了一個快出來,在向其中進行修改,修改ID號,修改程序的代碼段等等,因此就有了父進程和子進程。

七、fork函數的使用

(1)fork函數調用一次,會返回兩次,返回值等於0的就是子進程,返回值大於0的就父進程。
咱們程序 p1 = fork();後,在運行到這一句的時候,操做系統就已經將父進程就是當前的進程複製了一份了,子進程就出來了,這個時候在這個程序中,父進程也就是如今的這個程序還在操做系統的中運行這,同時子進程也被操做系統運行着了,因此當咱們後面一次判斷 if(p1 == 0) 的時候,就是判斷這個是子進程仍是父進程,0就是子進程,if(p1 > 0)就是判斷是不是父進程

(2)由於當咱們fork的時候,這個進程的整個進程控制塊都被複制了,也就表明着這個程序的代碼段也被複制了一份,變成了子進程,這個子進程有的也是這個代碼段,因此p1 = fork()後面的代碼是兩個進程同時擁有的,兩個進程都要運行的,因此有了if (p1 == 0)和if(p1 > 0 )這兩個if來讓程序進到其中的某一個進程中,由於父進程中的p1是大於0的,子進程中的p1是等於0的,這樣就能夠進到其中的某一個進程中。fork後有了兩個進程,都在運行的,宏觀上的並行,微觀上的並行,由操做系統來調度。

再次總結:
fork函數運行後,就將父進程的進程控制塊複製了一個份成爲了子進程的進程控制塊,這個時候,子進程的進程控制塊中就有了父進程控制塊的代碼段,而且這個時候已是兩個進程了,因此操做系統就會對這兩個進程進行調度運行,因此這個時候兩個進程都是在運行的,因此fork後面的代碼段兩個進程都有,都會運行,這個時候咱們若是想要進到這兩個進程中某一個進程中的話,就須要用到if來進行判斷,由於fork會返回兩次,一次的返回值給了父進程,一次的返回值給子進程,給父進程的返回值是大於0的,給子進程的返回值是等於0的,由於後面的代碼兩個進程都會運行,若是咱們想要進入到某一個進程中去作事情的話,就能夠判斷返回值是父進程的仍是子進程的來決定究竟是哪一個進程的,重而能夠進入到這個進程中去。父進程的返回值是大於0的,值就是本次fork建立子進程的pid

一、五、父子進程對文件的操做

一、子進程繼承父進程中打開的文件

(1)在父進程中open打開一個文件獲得fd,以後fork父進程建立子進程,以後再父子進程中wirte向文件中寫東西,發現父子進程是接續寫的,緣由是由於父子進程中的fd對應的文件指針彼此是關聯的,很像加上了O_APPEDN標誌。

(2)咱們父子進程寫的時候,有的時候會發現相似於分別寫的狀況,那是由於父子進程中的程序過短了,能夠加一額sleep(1)來休眠一段時間,主要是由於,你的父進程或者子進程在運行完本身的程序的時候,就會close關閉這個文件了,既然有一個進程把文件關閉了,那另外一個進程既然寫不進去了

二、父子進程各自獨立打開同一個文件

(1)父進程open打開一個文件而後寫入,子進程也open打開這個文件而後寫入,結論是分別寫,會覆蓋,緣由是由於這個時候父子進程已經徹底分離後纔在各自的進程中打開文件的,這時兩個進程的PCB已是徹底獨立的了,因此至關於兩個進程打開了同一個文件,實現了文件共享。open中加上O_APPEND標誌後,能夠實現接續寫,實現文件指針關聯起來

一、六、進程的誕生和消亡

一、進程的誕生

(1)進程0和進程1,進程0是在內核態的時候,內核本身弄出來的,是空閒進程,進程1是內核複製進程0弄出來的,至關於內核中的fork弄出來的,而後執行了進程1中的那個根文件系統中init程序,進而逐步的重內核態到用戶態。
(2)進入到了用戶太的時候,以後的每個進程都是父進程fork出來的
(3)vfork,和fork有微小的區別,須要能夠本身去百度

二、進程的消亡

(1)正常終止和異常終止,靜態的放在那裏不動的a.out叫作程序,運行了就叫作進程,進程就是運行的程序
(2)進程在運行的時候是須要耗費系統資源的(內存、IO),內存是指進程中向操做系統申請的內存的資源,建立進程時也須要內存,IO是進程對文件IO和IO硬件設備,好比串口這個IO的資源。因此進程終止的時候,應當把這些資源徹底釋放,否則的話,就會不斷的消耗系統的資源,好比你的進程死的時候,你沒有將向操做系統申請的資源換回去,那麼內存就會越用越少,造成吃內存的狀況
(3)linux系統中,當一個進程退出的時候,操做系統會自動回收這個進程涉及到的全部資源,好比,向咱們在一個進程malloc的時候,可是沒有free,可是這個進程退出的時候,內存也會被是釋放,好比open了一個文件,可是進程結束時咱們沒有close,可是進程結束時,操做系統也會進行回收。可是操做系統只是回收了這個進程工做時消耗的資源,並無回收這個進程自己佔用的內存(一個進程的建立須要內存,由於是複製父進程的PCB出來的,主要是task_struct和棧內存,有8KB,task_struct就是PCB,描述這個進程的那個結構體,棧內存是這個進程所獨有的棧)。
(4)因此進程消耗的資源主要是分爲兩部分,一部分是這個進程工做時消耗的資源,另外一部分是這個進程自己本身存在就須要的資源,操做系統在一個進程結束的時候,回收的只是進程工做時用的資源,並無回收進程自己本身存在所須要的資源
(5)因此進程本身自己須要的8KB內存,操做系統沒有回收,是須要別人輔助回收的,因此須要收屍的人,收進程屍體的人,由於進程的屍體也須要佔用8KB的內存,因此須要收屍,這我的就是這個進程的父進程

三、殭屍進程(子進程結束了,可是父進程還沒來的及將子進程回收,那8KB內存父進程尚未回收)

(1)殭屍進程就是子進程先與父進程結束的進程:子進程結束後,父進程並非立刻馬上就將子進程收屍的,在這一段子進程結束了,可是父進程還沒有幫子進程收屍的這一段時間,這個子進程就是一個殭屍進程
(2)在殭屍進程的這一段時間,子進程除了8KB這一段內存(task_struct和棧)外,其他的內存空間和IO資源都被操做系統回收乾淨了
(3)父進程可使用wait或者waitpid以顯式的方式回收子進程的待被回收的內存資源,而且獲取子進程的退出狀態。真由於父進程要調用wait或者waitpid來幫子進程回收子進程結束時剩餘的內存資源,因此父進程也是要執行函數的,因此有了子進程的這一段殭屍進程的時間,由於子進程死的太快了,父進程須要等到調用了這個兩個函數才能夠回收子進程,因此子進程必然會存在殭屍進程的這一段時間。
(4)子進程結束的階段到父進程調用wait或waitpid函數的這一階段,就是子進程的殭屍進程階段
(5)父進程還有一種狀況也能夠回收子進程剩餘的內存資源,就是父進程也死了,這個時候,父進程結束時也會去回收子進程剩餘的內存資源,這種回收,是在子進程先結束了,父進程後結束的狀況下 。(這樣設計是爲了防止,父進程活的時候忘記使用wait或waitpid來回收子進程從而形成內存泄漏)

四、孤兒進程

(1)父進程先於子進程結束
(2)Linux中規定,全部的孤兒進程都自動成爲進程1,init進程的子進程

一、七、父進程wait回收子進程

一、wait的工做原理,由於父子進程是異步的,因此父進程要wait阻塞等待信號後喚醒在回收

(1)子進程結束時,系統向其父進程發出SIGCHILD信號(SIGCHILD是信號中的一個編號)
(2)父進程調用wait函數後阻塞,wait這個函數是阻塞式的,父進程調用wait這個函數阻塞在這裏,等操做系統向我發SIGCHILD信號
(3)父進程wait後阻塞等到操做系統發來的SIGCHILD信號,收到SIGCHILD信號後,父進程被喚醒,去回收殭屍子進程
(4)父進程若是沒有任何的子進程,這個時候父進程調用wait函數,wait函數就會返回錯誤

二、wait函數

(1)wait的參數,status。這個參數是用來返回狀態的,是子進程結束時的狀態,父進程調用wait經過status這個參數,能夠知道子進程結束時的狀態。子進程結束的時候有兩種狀態,一種是正常的結束狀態,就是return,exit等形成的結束,一種是異常狀態,就是由信號形成的異常終止狀態,經過status參數返回的狀態能夠知道這個殭屍子進程是怎麼死的
(2)wait的返回值,pid_t,就是本次wait回收的殭屍子進程的PID號。由於當前進程可能有多個子進程,wait阻塞後,你也不知道未來哪個子進程會結束先讓你回收,因此須要用wait的返回值來表示本次回收的殭屍子進程的PID號,來知道是哪一個進程本次被回收了。

因此:wait函數就是用來回收殭屍子進程的,父進程wait後阻塞等到操做系統發來的SIGCHILD信號,收到SIGCHILD信號後,父進程被喚醒,去回收殭屍子進程,而且還會經過返回值pid_t類型的值獲得這個殭屍子進程的PID,還會經過status參數獲得這個子進程的結束的狀態

(3)WIFEXITED、WIFSIGNALED、WEXITSTATUS,這幾個宏是來判斷wait函數中status參數返回回來的值表明子進程結束是哪一種狀態的
WIFEXITED:能夠用WIFEXITED宏和status參數值來判斷回收的那個子進程是不是正常終止的(return exit _exit退出的),測試status的值是正常退出,這個宏就返回1,不正常就是0。
WIFSGNALED: 用來判斷子進程是否非正常終止,是不是不正常終止(被信號所終止),是非正常的就返回1,不是就0
WEXITSTATUS:這個宏能夠獲得子進程正常結束狀態下的返回值的(就是return exit _exit 帶的值)

3.wait和waitpid的差異

*(1)wait和waitpid的功能幾乎是同樣的,都是用來回收殭屍子進程的。不一樣的是waitpid能夠指定PID號,來回收指定的殭屍子進程,waitpid能夠有阻塞和非阻塞兩種工做方式
pid_t waitpid(pid_t pid, int
status, int options);
返回值是被回收的子進程的PID號,第一個參數是指定要回收子進程的PID號,若是第一個參數給了-1,就表示無論哪個子進程要被回收均可以,就跟wait同樣了,第二個參數會獲得一個子進程的結束狀態,第三個參數是一個可選功能,能夠有讓這個函數是阻塞仍是非阻塞的,第三個參數若是是0的話,就表示沒有特別的要求,表示是阻塞式的
WNOHANG,options是這個就表示是非阻塞式的,若是第一個參數給的子進程號,這個子進程沒有執行完,沒有可回收的就立馬返回 ,返回值是0,若是個的子進程號是不對的,就返回-1,若是給的子進程號被成功回收了,就返回這個被回收的子進程的PID號**

四、競態的概念

(1)競態的全稱就是競爭狀態,在多進程的環境下,多個進程會搶佔系統的資源,內存,文件IO,cpu。
(2)競爭狀態是有害的,咱們應該去消滅這種競態,操做系統爲咱們提供了不少機制來去消滅這種競態,利用好sleep就可讓父子進程哪一個先執行,哪一個後結束,固然還有其餘的方法,就是讓進程同步

一、八、exec族函數

一、既然是一個族函數,就是有不少函數是以exec開頭的

二、咱們fork一個子進程,是爲了讓子進程執行一個新的程序,爲了讓OS調度,因此建立子進程的目的是爲了實現父進程和子進程在宏觀上的並行,爲了讓子進程執行新的程序,因此咱們能夠有兩種實現方法:一種是用if直接進到子進程中,在子進程中直接寫新的程序的代碼。可是這樣很差,由於須要將代碼全都寫到子進程的這個if中,若是代碼太多的會也會很差控制。第二種:就是能夠直接用exec族函數來直接運行可執行程序,能夠在子進程中,直接使用exec族函數,加載可執行程序來直接運行,不須要源代碼。

三、因此有了exec族函數,咱們能夠將子進程要執行的程序,直接進程單獨的編寫好,編譯鏈接成一個執行程序,完了再子進程中用exec族的函數能夠直接運行這個可執行程序,實現了多麼方便的方法當一個進程CTRL + C 退出不了後,能夠先ps看下這個進程,而後kill -9 xxx 能夠關閉一個進程在命令航洗啊

四、exec族的6個函數

(1)execl和execv:這兩個函數主要是第二個參數的格式的問題,第一個函數中的執行的那個可執行程序所帶的參數,是一字符串,一個字符串表明一個參數,多個參數,是多個字符串,像列表同樣,一個參數一個參數的,參數的結尾必須是NULL表示傳參結束了。execl中的l其實就是list的縮寫,列表。
而execv是將參數放到一個字符串數組中,
這個兩個函數,若是找到了那個pathname的可執行程序,就會執行,找不到就會報錯,可執行程序是咱們指定的。注意是全路徑
(2)execlp和execlp:這連個函數的區別就是多了個p,這個函數的第一個參數是file,是一個文件名,固然也能夠是全路徑加文件名,這兩個函數由於參數是文件名,因此會首先找file這個名的可執行程序,找到後就執行,若是找不到就會去PATH環境變量指定的目錄下去尋找,也就是說這兩個函數執行的那個可執行程序應該是惟一的,若是你肯定只有這一個程序,就應該使用這個,方便多些路徑的麻煩
(3)execle和execpe:這兩個函數就是多了一個e,多了一個環境變量的字符串數組,能夠給那個可執行程序多傳遞一個環境變量,讓那個可執行程序在運行的時候,能夠用這個傳過去的環境變量

extern char **environ;這個是進程中的那個進程環境表,當前進程的全部環境變量都維護在這個表中,這個environ指針指向那個環境表。本身是就是一個字符串數組指針,就是一個指針數組

@1:int execl(const char path, const char arg, ...);
(1)第一個參數是要執行的那個可執行程序的全路徑,就是pathname,第二參數是要執行的那個可執行程序的名字,第三個是變參,說明那個可執行程序能夠帶的參數,是由那個可執行程序決定,他能接受幾個,你就能夠傳幾個,參數是以NULL結尾的,告訴傳參沒了,能夠在命令行下,用which xxx來查這個xxx命令(也是程序)的全路徑,必定要是全路徑第一個參數

用法是:execl("/bin/ls", "ls", "-l", "-a", NULL);

@2:int execlp(const char file, const char arg, ...);
用法只是第一個參數不一樣,第一個參數是要執行可執行程序的名字,能夠是全路徑,也能夠是單純的名字,先去PATH環境變量下的路徑中去尋找,若是沒有找到這個程序就在指定的目錄下或者當前目錄下找
@3:int execle(const char path, const char arg, ..., char * const envp[]);

@4:int execv(const char path, char const argv[]);
(1)第一個參數和execl同樣,第二參數說明,參數是放在argv這個指針數組中的,字符串數組,數組中的指針指向的東西是能夠變的。用的時候是這樣用的,先定義一個
char * const arg[] = {"ls", "-l", "-a", NULL};
而後在 execv("/bin/ls", arg);

@5:int execvp(const char file, char const argv[]);

@6:int execvpe(const char file, char const argv[], char *const envp[]);

(3)execle和execpe br/>@1:真正的main函數的原型是這樣的
int main(int argc, char argv, char env)

env就是給main函數額外傳遞的環境變量字符串數組,正常咱們直接寫一個程序直接編譯運行的時候裏面的環境變量值是繼承父進程的,最先是來源於操做系統中的環境變量
@2:execle 的用法,
在子進程中先定義了一個 char * const envp["AA=XX", "BB=DDD", NULL];
在使用execle("/bin/ls", "ls", "-l", "-a", NULL, env);給ls這個可執行程序傳遞了環境變量envp中的,這個時候這個執行的程序中的env[0]就等於了AA=XX env[1]就等於了BB=DDD,NULL表示後面沒有要傳的環境變量,用來當結束。若是你不給這個可執行程序傳遞環境變量的話,這個可執行程序繼承的默認就是父進程中的那一份,若是你傳了,就是你傳遞的那一份環境變量

一、九、進程的狀態和system函數

一、進程的幾個重要的須要明白的狀態
操做系統是怎麼調度進程的,操做系統中有一個就緒鏈表,每個節點就是一個進程,將全部符合條件能夠運行的進程加到了這個就緒鏈表中。
操做系統中還有一個鏈表,是全部進程的鏈表,就是全部的進程都在這個鏈表中,這個鏈表中的進程若是有符合就緒態能夠運行的了,就會被複制加載到就緒態鏈表中
(1)就緒態:這個進程當前的全部運行條件具有,只要獲得CPU的時間,就能夠運行了,只差被OS調度獲得CPU的時間了
(2)運行態:進程從就緒態獲得了CPU,就開始運行了,當前進程就是在運行態
(3)殭屍態:進程運行結束了,操做系統回收了一部分資源(進程工做時用的內存和IO資源),可是沒有被完全回收,父進程還沒將剩餘的8KB內存空間(進程自身佔用的內存)回收的這段時間。
(4)等待態(淺度睡眠&深度睡眠):進程等待知足某種條件達到就緒態的這段時間,等待態下就算給CPU時間也無法運行。淺度睡眠時,進程能夠被(信號,別人打電話告訴你不要等那個衛生紙了,你就不等了)喚醒達到就緒態,獲得CPU時間,就能夠達到運行態了。深度睡眠時,進程不能夠被喚醒了(別人打電話告訴你不要等那個衛生紙了,你就非要等到那個衛生紙才行),不能再喚醒達到就緒態了,只有等待到條件知足了,才能結束睡眠狀態
(5)暫停態:暫停態不是說進程終止了,進程終止了就成爲殭屍態了纔對,暫停態只是說進程被信號暫停了,還能夠恢復(發信號)

一個進程在一個生命週期內,是在這5種狀態間切換的

二、system函數

(1)system函數 = fork + exec
system函數是原子操做的,而fork+exec操做是非原子操做的
(2)原子操做的意思是,一旦開始,就會不被打斷的執行完。fork + exec是非原子的,意思是說你fork後獲得一個子進程的時候,CPU的時間有多是還沒執行exec呢,就被調度執行別的進程去了,而system函數是,你建立了子進程讓子進程執行那個程序,這之間是馬上的事情,而且執行完
(3)原子操做的好處就是會盡可能的避免競爭狀態,壞處就是本身連續佔用CPU的時間太長了
(4)int system(const char *command);
system函數先調用fork建立一個子進程,再讓這個子進程去執行command參數的命令程序,成功運行結束後返回到調用sysytem的進程。詳情使用的話,去百度看就行。

三、進程關係

(1)無關係,沒有如下三種關係234的狀況下,能夠認爲無關係。無關係就是說,進程間是獨立的,進程和進程之間不能夠隨便的訪問被的進程的地址空間,不能隨便訪問別進程中的東西
(2)父子進程關係
(3)進程組(group):由不少個進程構成了一個進程組,爲了讓這些進程間的關係更加密切,組外的不能夠隨便的訪問
(4)會話(session):會話就由不少個進程組構成的一個組,爲了讓一個會話作的事情,會話外的不能隨便訪問

一、十、守護進程

一、進程查看命令ps

(1)單獨ps只能看到當前終端的進程,當前是哪一個終端,哪一個進程,對應的時間和進程程序是什麼
(2)經常使用的ps帶的參數:
ps -ajx:偏向於顯示操做系統各類進程的ID號
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
父進程ID 子進程 進程組 會話ID 終端 用戶ID 時間 對應的程序名

ps -aux:偏向於顯示進程所佔系統的資源
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
當前用戶 進程ID CPU 內存 虛擬內存大小

二、向進程發信號的命令kill

(1)kill -信號編號 進程ID //向一個進程發送信號
(2)kill -9 xxx //這個是比較經常使用的,向xxx進程發送編號爲9的信號,意思是關閉當前這個進程

三、什麼是守護進程

(1)daemon:守護進程的意思,簡稱d,進程名後面帶d的基本表示是一個守護進程
(2)長期運行(通常開機運行直到關機時關閉),而普通進程如ls,運行完一遍進程就結束了
(3)與控制檯脫離,也就是說,終端關閉了,守護進程還在運行呢,而其餘普通進程,終端關了,進程就關了,普通進程和運行該進程的終端是相綁定的,終端關閉了,這個終端中的全部普通進程都會關閉,緣由仍是在於會話這個概念,由於一個終端中的全部進程實際上是一個會話,而守護進程是不會被關閉的
(4)服務器(server):守護進程通常是用來實現一個服務器的,服務器就是一個一直在運行的程序,當咱們須要某種幫助的時候,服務器程序能夠爲咱們提供,好比當咱們的程序想要nfs服務器的幫助實現nfs通訊,咱們就能夠調用服務器程序獲得服務器程序的幫助,能夠調用服務器程序來進行這些服務操做。
服務器程序通常都是守護進程,都是被實現成守護進程,因此具備長期運行和終端控制檯脫離的特色,因此守護進程其實就是一種特殊的進程,不受終端控制檯約束的進程,爲咱們整個系統提供某種服務,當咱們須要某種服務的時候,就能夠調用這個守護進程的服務程序來進行獲得幫助

(4)咱們本身也能夠實現一個守護進程,好比你寫的這個程序,你也想達到某種服務,脫離終端控制檯的影響,就能夠將其設計成守護進程。

四、常見的守護進程就先說兩個

(1)syslogd,是系統日誌守護進程,提供syslogd功能
(2)cron, 這個守護進程使用來實現操做系統的一個時間管理的,好比在Linux中要實現一個定時時間到了才執行程序的功能就須要cron,好比按期垃圾清理,就能夠定好時間,沒到這個時間就會執行垃圾清理的這個程序,就要用到cron

守護進程其實就是一個能長期運行,能脫離控制檯的約束的一個進程,能夠長期運行,咱們須要的時候,能夠經過調用守護進程來獲得想要的。由於一直在運行爲咱們服務,因此能夠說每個守護進程是一個服務程序,不少個守護進程在一塊兒,就造成了服務器

五、編寫簡單的守護進程

(1)每個守護進程均可以由一個普通進程實現成一個守護進程,可是通常狀況下,只有你的這個進程你想讓他一直長期運行,最終和其餘的守護進程一塊兒構成一個服務器的狀況下,纔會讓他變成守護進程

(2)咱們能夠寫一個函數,create_daemon,目的是讓一個普通進程一調用這個函數,就會被實現變成守護進程br/>(3)create_daemon函數實現的要素
@1:子進程等待父進程退出
@2:子進程使用setsid將當前的進程設置爲一個新的會話期session,目的是讓當前的進程脫離控制檯,
@3:調用chdir將當前工做目錄設置爲/, chdir("/");目的是讓其不依賴於別的文件系統,設置成根目錄就可讓開機時就運行,並且不依賴於別的文件系統br/>@4:umask設置爲0以取消任何文件權限屏蔽,確保未來這個進程有最大的文件操做權限
@5:關閉全部文件描述符,由於有可能在建立守護進程以前,調用這個函數的父進程可能已經打開了一些文件了,被當前的進程繼承了,若是不關閉的話,這個文件OS就會認爲一直有人打開着,由於守護進程是一直在運行的,關閉的文件描述包括0、一、2,關閉方法:

for (i=0; i<xxx; i++)
{
    close(i);
}

xxx是當前系統所擁有的一個進程文件描述符的上限,這個是一個不定值,因此咱們要動態獲取當前系統對一個進程的文件描述符的最大值。經過一個函數來獲取,這個函數叫sysconf,這個函數能夠獲取當前操做系統的全部配置信息的,原型是 long sysconf(int name);你要獲取哪個參數的信息,就把這個參數的名字傳進去就行,參數的名字是用宏來體現的,能夠經過man手冊來查找,獲取系統所容許打開的一個進程文件描述符的上限數目的宏是OPEN_MAX或者_SC_OPEN_MAX,函數的返回值就是想要的數目

@6:將0、一、2定位到/dev/null。 這個設備文件至關於OS中的一個垃圾堆。方法是:
open("/dev/null") //對應返回的文件描述符是0
open("/dev/null") //對應返回的文件描述符是1
open("/dev/null") //對應返回的文件描述符是2

六、使用sysconf來記錄調試信息

(1)openlog函數

`void openlog(const char *ident, int option, int facility);`
@1:打開一個日誌文件
@2:第一個參數是打開這個文件的程序的名字
@3:第二參數和第三個參數都是一些選項,用宏定義來標識的,看man手冊
@4:option
     LOG_CONS 這個宏,當日志自己壞了時候,或者寫不進去的時候,就會將錯誤信息輸出到控制檯
     LOG_PID  這個宏,咱們寫入到日誌中信息每一條都會有咱們寫入日誌文件的這個進程的PID
@5:facility  表示當前寫入到日誌中的信息,是一個什麼樣的log信息,和什麼有關的
     LOG_AUTH 有這個宏,表示是和安全,檢驗,驗證有關的
     LOG_CRON   這個宏,表示是和定時的守護進程相關的信息 clock daemon(cron an at)
     LOG_FTP    若是是一個FTP服務器相關的信息,就是這個宏
     LOG_USER   若是咱們寫入到日誌文件中的日誌信息是普通的用戶層的不是什麼特殊的,就這個宏

(2)syslog函數 將信息輸出到日誌文件中

void syslog(int priority, const char *format, ...);
@1:第一個參數表示這條日誌信息的重要程度,也是用宏來標識的
@2:LOG_DEBUG 有這個宏表示是最不重要的日誌信息
@3:LOG_INFO  有這個宏表示系統的正常輸出消息
@4:LOG_NOTICE 有這個宏表示這條日誌信息,是比較顯著的,比較顯眼的,公告
@5:LOG_WARNING 是一條警告,要注意了
@6:LOG_ERR 錯誤了,出錯了
@7:LOG_CRIT 出現緊急的狀況了,要立刻去處理
@8:LOG_ALERT 馬上要去行動了,否則會完蛋
@9:LOG_EMERG 系統已經不行了

(3)closelog函數 關閉log文件

void closelog(void);

通常log信息都在OS的/var/log/messages這個文件中存着的,可是Ubuntu中log信息是在/var/log/syslog文件中存着的。都是在虛擬文件系統中

(3)syslog的工做原理

操做系統中有一個syslogd守護進程,開機運行,關機結束這個守護進程來負責日誌文件的寫入和維護
,syslogd是獨立運行的一個進程,你不用他,他也在運行。咱們當前的進程能夠經過調用openlog這個系統調用來打開一條和syslogd相鏈接的通道,而後經過syslog向syslogd這個守護進程發消息,而後syslogd在將消息寫入到日誌文件系統中
因此syslogd其實就是日誌文件系統的一個服務器進程,提供日誌服務的。

七、讓程序不能屢次運行

(1)咱們弄好了守護進程的時候,守護進程是長時間運行不退出的,除非關機,因此咱們運行了幾回守護進程,就會出現幾個守護進程,這樣是很差的。
(2)咱們但願一個程序是有單例運行功能的,就是說咱們以前運行了一程序後,已經有了一個進程了,咱們不但願在出現一個這個進程了,在運行的這個程序的時候就會直接退出或者提示程序已經在運行了
(3)方法就是,在運行這個程序的時候,咱們在這個程序的裏面開始的位置,咱們判斷一個文件是否存在,若是這個文件不存在的的話,咱們的程序就會運行,而且建立這個文件,就會出現一個進程,若是這個文件存在的話,就會退出這個程序,不讓其繼續運行,這樣就會保證有了單例功能。
(4)能夠在一個程序的開始加上一個open("xxx", O_RDWR | O_TRUNC | O_CREAT | O_EXCL, 0664);
若是這個文件存在的話,就被excl這個標誌影響,open就會錯誤,咱們能夠在後面用if來判斷errno這個值是否和EEXIST這個宏相等若是和這個宏相等了,就說明是文件已經存在引發的錯誤,errno是在errno.h中定義的。當咱們這個進程運行結束時,咱們讓這個進程在結束以前在把這個文件刪除掉,刪除一個文件用的函數是remove函數,咱們用atexit函數來註冊一個進程結束以前運行的一個清理函數,讓這個函數中用remove函數來刪除這個文件

一、十一、Linux進程間通訊(IPC)

一、進程間通訊,就是進程和進程之間的通訊,有多是父子進程之間的通訊,同一個進程組之間的通訊,同一個會話中的進程之間的通訊,還有多是兩個沒有關聯的進程之間的通訊.

二、以前咱們都是在同一個進程之間通訊的,都是出於一個地址空間中的,譬如在一個進程中的不一樣模塊中(不一樣的函數,不一樣的文件a.c,b.c等之間是用形參和實參的方式,和全局變量的方式進行通訊的,a.c的全局變量,b.c中也能夠用),最後造成了一個a.out程序,這個程序在運行的時候就會建立一個進程,進程裏去運行這個程序。這是一個進程中的通訊。
三、兩個進程之間的通訊,兩個進程是處在兩個不一樣的地址空間中的,也就是a進程中的變量名字和值在a進程的地址空間有一個,可是在b進程中的變量名字能夠在b進程的地址空間中和a進程的變量名字同樣,由於二者是在兩個不一樣的地址空間中的,每個進程都認爲本身獨自享有4G的內存空間,這二者實現的進程間通訊就不能經過函數或者全局變量進行通訊了,由於互相的地址看不到,因此兩個進程,兩個不一樣地址空間的通訊是很難的。
四、通常像大型的程序纔會用多進程之間的通訊,像GUI,服務器之類的,其餘的程序大部分都是單進程多線程的

五、Linux中提供了多種進程間通訊的機制
(1)無名管道和有名管道,這兩種管道能夠提供一個父子進程之間的進程通訊
(2)systemV IPC: 這個unix分裂出來的內核中進程通訊有:信號量、消息隊列、共享內存
(3)Socket域套接字(是BSD的unix分支發展出來的),Linux裏面的網絡通訊方式。最先Linux發明這個是爲了實現進程間通訊的,實際上網絡通訊也是進程間的通訊,一個電腦上的瀏覽器進程和另外一個電腦上的服務器程序進程之間的通訊
1和2只能是同一個操做系統之間的不一樣的進程之間的通訊,3是能夠實現兩個電腦上的不一樣操做系統之間的進程間通訊
(4)信號,a進程能夠給b進程信號或者操做系統能夠給不一樣的進程發信號

六、Linux的IPC機制(進程間通訊)中的管道
一、說管道的話通常指的是無名管道,有名管道咱們通常都會明確的說有名管道,有名管道是fifo
二、管道(無名管道):咱們之間進程之間是不能直接一個進程訪問到另外一個進程的地址空間而後實現進程間通訊的,因此這種管道的通訊方式的方法就是,在內核中維護了一個內存區域(能夠當作是一個公共的緩衝區或者是一個管道),有讀端和寫端,管道是單向通訊的,半雙工的,讓a進程和b進程經過這塊內存區域來實現的進程間的通訊。
(1)、管道信息的函數:pipe、write、read、close、pipe建立一個管道,就至關於建立了兩個文件描述符,一個使用來讀的,一個是用來寫的,a進程兩個文件描述符,b進程兩個文件描述符,因此是a進程在用寫的文件描述符像內核維護的那一個內存區域(管道)中寫東西的時候,b進程要經過他的讀文件描述符read到管道中的a進程寫的內容。因此也就是半雙工的通訊方式,可是咱們爲了通訊的可靠性,會將這一個管道變成單工的通訊方式,將a進程的讀文件描述符fd=-1,不用這個讀文件描述符,將b進程的寫文件描述符等於-1,也不用這個,實現了單工的通訊方式,這樣能夠提升通訊的可靠性,若是想要雙工的通訊,就在pipe建立一個管道,將a進程中的寫文件描述符fd廢棄掉,將b進程的讀文件描述符廢棄掉,這樣兩個管道一個實現從左往右的通訊,一個實現從右往左的通訊,就實現了雙工,同時也提升了通訊的可靠性。
(2)、管道通訊只能在父子進程之間通訊
(3)管道的代碼實現思路:通常是先在父進程中pipe建立一個管道而後fork子進程,子進程繼承父進程的管道fd

三、有名管道(fifo)
(1)解決了只能在父子進程之間通訊的問題
(2)有名管道,其實也是內核中維護的一個內存區域,不過這塊內存的表現形式是一個有名字的文件
(3)有名管道的使用方法:先固定一個文件名,而後咱們在兩個進程分別使用mkfifo建立fifo文件,而後分別open打開獲取到fd,而後一個讀一個寫。
(4)任何兩個進程均可以實現半雙工的通訊,由於有名管道是內核維護的一個內存區域,一個有名字的文件,因此咱們要先固定一個文件的名字(這個文件是真是存在的,咱們外在弄出來的),好比abcd.txt,而後咱們在兩個進程中分別用mkfifo(mkfifo的是同一個文件的名字)建立fifo管道文件,而後用open分別打開這個有名字的文件,一個進程獲得了兩個fd,而後一個讀一個寫
(5)有名管道的通訊函數:mkfifo、open、write、read、close

七、systemV IPC
(1)消息隊列:消息隊列其實就是內核中維護一塊內存區域,這個內存區域是fifo的,是一個隊列,咱們a進程向這個隊列中放東西,咱們b進程從這個隊列中讀東西,fifo(隊列)是先進先出的,是一種數據結構。能夠實現廣播,將信息發送出去,造成多個隊列。(適合於廣播,或者單播)
(2)信號量(適合於實現進程通訊間的互斥):信號量實質就是一個計數器,更本質的來講其實就是一個用來計數的變量,這個信號量通常是用來實現互斥和同步的。好比開始的時候這個信號量int a =1,當咱們a進程想要訪問一個公共的資源的時候,好比說是一個函數的時候,咱們a進程就會去判斷這個信號量a是否等於,若是等於1,a進程就知道當前這個公共的資源函數,沒有被別的進程使用,a進程就會用這個函數,當用的時候,a進程會將信號量a=1,變成一個其餘的值,這樣b進程這個時候若是也想用這個公共的資源的時候,就會發現a這個信號變了,因此
b進程就知道有別的進程用這個函數了,因此b進程就會等,等到a進程用完了以後,a進程就會信號量a的變成原先的1,這樣b進程就知作別的進程用完了,以後b進程就會用這個函數,這樣的相似的思路,就能夠實現互斥的概念。同步也是能夠實現的用信號量,好比a進程走一步,就將信號量a的值加1,b進程來判斷信號量a是否加1了,若是加1了,b進程也跟着走一步,b走完之後就將信號量的值恢復到原先,a開始走,a將信號量加1,b又走,b又將信號量減1,由於兩個進程之間自己是異步的,由於互相都看不到對方在作什麼,可是能夠經過信號量的這個機制,來實現節奏上的同步,隊列的感受。
(3)共享內存(適合於進程通訊時須要通訊大量的信息量時),跟上面兩種的共享的內存不一樣的是,這個共享內存是大片的內存。兩個進程間的的虛擬地址同時映射到了物理內存中一大片

八、剩餘兩類IPC
(1)信號
(2)unix域套接字,socket

2.python中的多進程

# multiprocessing內置模塊包含了多進程的不少方法
from multiprocessing import Process
import os
import time

def func(*args, **kwargs):
    while True:
        time.sleep(1)
        print('子進程 pid: ', os.getpid()) # 查看當前進程的pid
        print('func的父進程是: ', os.getppid())  # 查看父進程的pid
        print(args)
        print(kwargs['haha'])

if __name__ == '__main__':  # 在windows操做系統上,建立進程啓動進程必須須要加上這句話,其餘操做系統不須要
    # target爲要綁定註冊的函數(進程), args爲要傳給綁定註冊的函數(進程)的位置參數. kwargs爲要傳給綁定註冊的函數(進程的動態關鍵字參數),kwargs參數是一個字典類型
    p = Process(target=func,args=(5, 4, 3, 2, 1), kwargs={'haha' : 'hehe'})    # 將func函數註冊到進程後獲得一個進程對象p # 建立了一個進程對象p
    p.start()   # 開啓了子進程,此時func就是相對於本父進程的另一個進程,屬於另一個應用程序,操做系統建立一個新的進程func子進程,而且子進程進入就緒態,當有時間片時(也就是其餘進程(這裏是父進程)阻塞時),func子進程就開始執行了
    while True:
        time.sleep(2)
        print('父進程 pid: ', os.getpid()) # 查看當前進程的pid
        print('__main__的父進程是: ', os.getppid())  # 查看父進程的pid
相關文章
相關標籤/搜索