Linux進程簡介
進程是操作系統(tǒng)中的一個重要概念,它是一個程序的一次執(zhí)行過程,程序是進程的一種靜態(tài)描述,系統(tǒng)中運行的每一個程序都是在它的進程中運行的。
進程4要素
要有一段程序供該進程運行
進程專用的系統(tǒng)堆??臻g
進程控制塊(PCB),具體實現(xiàn)是task_struct結(jié)構(gòu)
有獨立的存儲空間
Linux系統(tǒng)中所有的進程是相互聯(lián)系的,除了初始化進程外,所有進程都有一個父進程。新的進程不是被創(chuàng)建,而是被復(fù)制,或是從以前的進程復(fù)制而來。Linux中所有的進程都是由一個進程號為1的init進程衍生而來的。
Linux系統(tǒng)包括3種不同類型的進程,每種進程都有自己的特點和屬性:
交互進程:由一個Shell啟動的進程,既可以在前臺運行,又可以在后臺運行
批處理進程:這種進程和終端沒有聯(lián)系,是一個進程序列
監(jiān)控進程(守護進程):Linux啟動時啟動的進程,并在后臺運行
進程控制塊
在Linux中,每個進程在創(chuàng)建時都會被分配一個數(shù)據(jù)結(jié)構(gòu),稱為進程控制塊(PCB, Process Control Block),描述進程的運動變化過程,與進程是一一對應(yīng)的關(guān)系。通常PCB包含以下信息:
進程標識符:每個進程的唯一標識符,可以是字符串,也可以是數(shù)字。
進程當(dāng)前狀態(tài):為方便管理,相同狀態(tài)的進程會組成一個隊列,如就緒進程隊列。
進程相應(yīng)的程序和數(shù)據(jù)地址:以便把PCB與其程序和數(shù)據(jù)聯(lián)系起來。
進程資源清單:列出所有除CPU外的資源記錄,如擁有的I/O設(shè)備,打開的文件列表等。
進程優(yōu)先級:反映進程的緊迫程度,通常由用戶指定和系統(tǒng)設(shè)置。
CPU現(xiàn)場保護區(qū):當(dāng)進程因某種原因不能繼續(xù)占用CPU時,釋放CPU,需要將CPU的各種狀態(tài)信息保護起來。
進程同步與通信機制:用于實現(xiàn)進程間互斥、同步和通信所需的信號量等。
進程所在隊列PCB的鏈接字:根據(jù)進程所處的現(xiàn)行狀態(tài),進程相應(yīng)的PCB參加到不同隊列中,PCB鏈接字指出該進程所在隊列中下一進程PCB的首地址。
與進程有關(guān)的其它信息:如進程記賬信息,進程占用CPU的時間等。
通過ps命令可以查看系統(tǒng)中目前有多少進程正常運行
通過ps-aux命令可以查看每個進程的詳細信息
進程控制的相關(guān)函數(shù)
fork()函數(shù)
系統(tǒng)調(diào)用fork()函數(shù)派生一個進程,函數(shù)原型為:
#include 《sys/types.h》
#include 《unistd.h》
pid_t fork(void);
運行成功,父進程返回子進程ID,子進程飯0;運行出錯返回-1。
fork系統(tǒng)調(diào)用的作用是復(fù)制一個進程,從而出現(xiàn)兩個幾乎一樣的進程。一般來說,fork后是父進程先執(zhí)行還是子進程先執(zhí)行是不確定的,取決于內(nèi)核所實使用的調(diào)度算法。
fork函數(shù)示例,fork_test.c:
#include 《sys/types.h》
#include 《stdio.h》
#include 《stdlib.h》
#include 《unistd.h》
int main(void)
{
int count = 0;
pid_t pid;
pid = fork();
if(pid 《 0)
{
printf(“error in fork!”);
exit(1);
}
else if(pid == 0)
printf(“I am the child process, the count is %d, my process ID is%d
”, count, getpid());
else
printf(“I am the parent process, the count is %d, my process ID is%d
”, ++count, getpid());
return 0;
}
編譯后運行:
$ 。/fork_test
I am the parent process, the count is 1, my process ID is2308
I am the child process, the count is 0, my process ID is2309
在語句pid = fork();之前,只有一個進程在執(zhí)行代碼,但在該語句之后,有兩個進程在執(zhí)行之后的代碼,根據(jù)pid的不同執(zhí)行不同的語句。
fork調(diào)用的神奇之處在于被調(diào)用一次,能夠返回兩次,返回結(jié)果可能有3種情況:
父進程中:fork返回新創(chuàng)建的子進程的ID
子進程中:fork返回0
出現(xiàn)錯誤:fork返回負值
fork出錯的原因有2:
當(dāng)前進程數(shù)已達系統(tǒng)規(guī)定的上限,此時errno的值被設(shè)置為EAGAIN
系統(tǒng)內(nèi)存不足,此時errno的值被設(shè)置為ENOMEN
errno是Linux下的一個宏定義常量,當(dāng)Linux中C API函數(shù)發(fā)生異常時,一般會將errno變量賦值為一個正整數(shù)(需include),不同的值表示不同的含義,通過查看該值可推測出錯原因。
vfork()函數(shù)
vfork()與fork()的區(qū)別是:fork()需要復(fù)制父進程的數(shù)據(jù)段,而vfork()不需要完全復(fù)制,在子進程調(diào)用exec()或exit()之前,子進程與父進程共享數(shù)據(jù)段。fork()不對父子進程的執(zhí)行次序作限制,而vfork()調(diào)用后,子進程先運行,父進程掛起,直到子進程調(diào)用了exec()或exit()后,父子進程的執(zhí)行次序才不再有限制。
實際上,vfork()創(chuàng)建出的不是真正意義的進程,它缺少了進程4要素的最后一項——獨立的內(nèi)存資源。
vfork()創(chuàng)建父子進程共享數(shù)據(jù)段測試,vfork_test1.c():
#include 《sys/types.h》
#include 《stdio.h》
#include 《stdlib.h》
#include 《unistd.h》
int main(void)
{
int count = 1;
int child;
printf(“Before create son, the father‘s count is:%d
”, count);
child = vfork();
if(child 《 0)
{
printf(“error in vfork!”);
exit(1);
}
if(child == 0)
{
printf(“This is son, his pid is:%d and the count is:%d
”, getpid(),++ count);
exit(1);
}
else
printf(“After son, This is father, his pid is:%d and the count is:%d, and the child is:%d
”, getpid(), count, child);
return 0;
}
編譯后運行:
$ 。/vfork_test1
Before create son, the father’s count is:1
This is son, his pid is:2530 and the count is:2
After son, This is father, his pid is:2529 and the count is:2, and the child is:2530
可以看出,在子進程中修改了count的值,變?yōu)?,而父進程中count值也為2,說明父子進程共享count,即父子進程共享內(nèi)存區(qū)。
vfork()創(chuàng)建子進程導(dǎo)致父進程掛起測試,vfork_test2():
#include 《sys/types.h》
#include 《stdio.h》
#include 《stdlib.h》
#include 《unistd.h》
int main(void)
{
int count = 1;
int child;
printf(“Before create son, the father‘s count is:%d
”, count);
if(?。╟hild = vfork()))
{
int i;
for(i=0; i《100; i++)
{
printf(“This is son, the i is:%d
”, i);
if(i == 70)
exit(1);
}
printf(“This is son, his pid is:%d and the count is:%d
”, getpid(), ++count);
exit(1);
}
else
printf(“After son, This is father, his pid is:%d and the count is:%d, and the child is:%d
”, getpid(), count, child);
return 0;
}
編譯后運行:
$ 。/vfork_test2
Before create son, the father’s count is:1
This is son, the i is:0
This is son, the i is:1
This is son, the i is:2
。..省略
This is son, the i is:67
This is son, the i is:68
This is son, the i is:69
This is son, the i is:70
After son, This is father, his pid is:2541 and the count is:1, and the child is:2542
可以看出,父進程是等待子進程執(zhí)行完畢后才開始執(zhí)行。
exec函數(shù)族
Linux使用exec函數(shù)族來執(zhí)行新的程序,以新的子進程來完全代替原有的進程,exec函數(shù)族包含6個函數(shù):
#include 《unistd.h》
int execl(const char *pathname, const char *arg, 。..);
int execlp(const char *filename, const char *arg, 。..);
int execle(const char *pathname, const char *arg, 。.., char *const envp[]);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *filename, char *const argv[]);
int execve(const char *pathname, char *const argv[], char *const envp[]);
運行成功無返回,出錯返回-1。
函數(shù)中含義字母l的:其參數(shù)個數(shù)不定,參數(shù)由命令行參數(shù)列表組成,最v后一個NULL表示結(jié)束。
函數(shù)中含義字母v的:使用一個字符串?dāng)?shù)組指針argv指向參數(shù)列表,與含字母l的函數(shù)參數(shù)列表完全相同。
函數(shù)中含義字母p的:可以自動在環(huán)境變量PATH指定的路徑中搜索要執(zhí)行的程序,其第一參數(shù)filename為可執(zhí)行函數(shù)的文件名,注意其它函數(shù)的第一個參數(shù)pathname為路徑名
函數(shù)中含義字母e的:比其它函數(shù)多了一個字符串指針型的envp參數(shù),用于指定環(huán)境變量。
實際上,只有execve()函數(shù)才是真正意義上的系統(tǒng)調(diào)用,其它都是在此基礎(chǔ)上經(jīng)過包裝的庫函數(shù)。與一般情況不同,exec函數(shù)族執(zhí)行成功后不會返回,因為調(diào)用進程實體,包括代碼段、數(shù)據(jù)段和堆棧段都被新的內(nèi)容取代,只是進程ID等一些表面上的信息仍保持原樣。
exec函數(shù)族使用舉例,exec_example.c:
#include 《unistd.h》
#include 《stdio.h》
int main(void)
{
char *envp[] = {“PATH=/tmp”, “USER=root”, “STATUS=testing”, NULL};
char *argv_execv[] = {“echo”, “excuted by execv”, NULL};
char *argv_execvp[] = {“echo”, “excuted by execvp”, NULL};
char *argv_execve[] = {“env”, NULL};
if(fork()==0)
{
if(execl(“/bin/echo”, “echo”, “executed by execl”, NULL))
perror(“Err on execl”);
}
if(fork()==0)
{
if(execlp(“echo”, “echo”, “executed by execlp”, NULL))
perror(“Err on execlp”);
}
if(fork()==0)
{
if(execle(“/usr/bin/env”, “env”, NULL, envp))
perror(“Err on execle”);
}
if(fork()==0)
{
if(execv(“/bin/echo”, argv_execv))
perror(“Err on execv”);
}
if(fork()==0)
{
if(execvp(“echo”, argv_execvp))
perror(“Err on execvp”);
}
if(fork()==0)
{
if(execve(“/usr/bin/env”, argv_execve, envp))
perror(“Err on execve”);
}
return 0;
}
上述程序用到了perror()函數(shù),它用來將函數(shù)發(fā)生錯誤的原因輸出到標準輸出(stderr),其函數(shù)原型為:
《pre class=“l(fā)ang_c”》#include 《stdio.h》
void perror(const char *s)
```
編譯后執(zhí)行:
$ 。/exec_example
PATH=/tmp
USER=root
STATUS=testing
executed by execl
executed by execlp
$ PATH=/tmp
USER=root
STATUS=testing
excuted by execvp
excuted by execv
由于各子進程執(zhí)行的順序無法控制,因而每次運行結(jié)果的輸出順序會有不同。
使用exec函數(shù)族,一般要加上錯誤判斷語句,因為exec函數(shù)易由多種原因運行失?。?/p>
找不到文件或路徑:errno被設(shè)置為ENOENT
數(shù)組argv和envp忘記使用NULL結(jié)束:errno被設(shè)置為EFAULT
沒有文件的運行權(quán)限:errno被設(shè)置為EACCES
exit()與_exit()函數(shù)
這兩個函數(shù)都是用于終止進程,其定義分別為:
#include 《stdlib.h》
void exit(int status);
#include 《unistd.h》
void _exit(int status);
兩者主要區(qū)別在于:
定義及所需頭文件不同
_exit()立即進入內(nèi)核;exit()則先執(zhí)行一些清除處理(包括調(diào)用執(zhí)行個終止處理程序,關(guān)閉所有標準I/O流等),然后進入內(nèi)核。
exit()在調(diào)用之前要檢查文件的打開情況,把文件緩沖區(qū)的內(nèi)容寫回文件;_exit()則直接使進程停止,清除其使用的內(nèi)存空間,并銷毀其在內(nèi)核中的各種數(shù)據(jù)結(jié)構(gòu)。
在Linux的標準函數(shù)庫中,有一套被稱為“高級I/O的函數(shù)”,如printf()、fopen()等,也被稱為“緩沖I/O(buffered I/O)”,其特征是對應(yīng)每一個打開的文件,在內(nèi)存中都有一片緩沖區(qū),每次會多讀出若干條記錄,當(dāng)達到一定的條件(如達到一定數(shù)量,或遇到特定字符,如‘ ’和文件結(jié)束符EOF)時,再將緩沖區(qū)的內(nèi)容一次性寫入文件,從而增加讀寫速度。但是,這種情況下,如果使用_exit()退出,會導(dǎo)致某些數(shù)據(jù)未被保存,而用exit()則不會有問題。
exit()與_exit()函數(shù)的區(qū)別測試,exit_differ.c:
#include 《sys/types.h》
#include 《stdio.h》
#include 《stdlib.h》
#include 《unistd.h》
int main(void)
{
pid_t pid;
if((pid=fork()) == -1)
{
printf(“failed to create a new process
”);
exit(0);
}
else if(pid == 0)
{
printf(“
child process, output begin
”);
printf(“child process, content in buffer”);
_exit(0);
}
else
{
printf(“parent process, output begin
”);
printf(“parent process, content in buffer”);
exit(0);
}
return 0;
}
編譯后執(zhí)行:
$ 。/exit_differ
parent process, output begin
parent process, content in buffer
child process, output begin
由于printf函數(shù)遇到’ ‘時才從緩沖區(qū)讀取數(shù)據(jù),在子進程中,因為_exit(0)直接將緩沖區(qū)的內(nèi)容清除了,內(nèi)容沒有顯示;而父進程中,執(zhí)行exit(0)之前會先將緩沖區(qū)的內(nèi)容顯示出來。
wait()與waitpid()函數(shù)
在一個進程調(diào)用了exit()之后,該進程并非立即消失,而是留下一個僵尸進程(Zombie)的數(shù)據(jù)結(jié)構(gòu),這時的一種處理方法就是使用wait()和waitpid()函數(shù)。
僵尸態(tài)是進程的一種特殊狀態(tài),沒有任何可執(zhí)行代碼,也不能被調(diào)度,僅僅在進程中保留一個位置,記載改進程的退出狀態(tài)等信息供其它進程收集。
wait()和waitpid()函數(shù)原型:
#include 《sys/types.h》
#include 《sys/wait.h》
pid_t wait(int *status);
pid_t waitpid(pid_t, int *status, int options);
運行成功返回進程ID,出錯返回-1。
參數(shù)status用于保存進程退出時的一些狀態(tài),如果只是想把進程滅掉,可以設(shè)置該參數(shù)為NULL。
參數(shù)pid用于指定所等待的線程。
pid取值含義
pid 》 0只等待進程ID為pid的子線程
pid = -1等待任何一個子線程,此時waitpid等價于wait
pid = 0等待同一個進程組中的任何子進程
pid 《 -1等待一個指定進程組中的任何子進程,其進程ID為pid的絕對值
參數(shù)options提供一些額外的選項來控制waitpid,包括WNOHANG和WUNTRACED兩個選項,這是兩個常數(shù),可以用|運算符連接使用。其中WNOHANG參數(shù)用于設(shè)置不等待子進程退出,立即返回,此時waitpid返回0;WUNTRACED參數(shù)用于配置跟蹤調(diào)試。
進程一旦調(diào)用wait后,就立刻阻塞自己,如果當(dāng)前進程的某個子進程已退出,則收集其信息,否則wait會一種阻塞在這里,直到有一個僵死進程出現(xiàn)。
wait()示例
wait_example.c:
#include 《sys/types.h》
#include 《sys/wait.h》
#include 《stdio.h》
#include 《stdlib.h》
#include 《unistd.h》
int main(void)
{
pid_t pc, pr;
if((pc = fork()) 《 0)
{
printf(“error in fork!”);
exit(1);
}
else if(pc == 0)
{
printf(“This is child process with pid of %d
”, getpid());
sleep(10);
}
else
{
pr = wait(NULL);
printf(“I catched a child process with pid of %d
”, pr);
}
exit(0);
}
編譯后執(zhí)行:
$ 。/wait_example
This is child process with pid of 10093
I catched a child process with pid of 10093
可以看到,第1行輸出后,等待大約10秒,第2行才輸出,這10秒就是子線程的睡眠時間。
waitpid()示例
父進程和子進程分別睡眠10秒鐘和1秒鐘,代表所作的相應(yīng)工作。父進程利用工作的簡短間歇查看子進程是否退出,如果退出就收集它。waitpid_example.c:
#include 《sys/types.h》
#include 《sys/wait.h》
#include 《stdio.h》
#include 《stdlib.h》
#include 《unistd.h》
int main(void)
{
pid_t pc, pr;
if((pc = fork()) == -1)
{
printf(“failed to create a new process”);
exit(0);
}
else if(pc == 0)
{
sleep(10);
exit(0);
}
do
{
pr = waitpid(pc, NULL, WNOHANG);
if(pr == 0)
{
printf(“No chiled exited
”);
sleep(1);
}
}while(pr == 0);
if(pr == pc)
printf(“successfully get child %d
”, pr);
else
printf(“some error occured
”);
return 0;
}
sdfgh
$ 。/waitpid_example
No chiled exited
No chiled exited
No chiled exited
No chiled exited
No chiled exited
No chiled exited
No chiled exited
No chiled exited
No chiled exited
No chiled exited
successfully get child 2711
可以看到,父進程經(jīng)過10次失敗嘗試后,終于收集到了退出的子進程。
獲取子進程返回狀態(tài)
對于wait()和waitpid()中的status參數(shù),當(dāng)其值不為NULL時,子進程的退出狀態(tài)會以int值的形式保存其中,通過一套專門的宏(macro)可以讀取存入的狀態(tài)值,這里只列舉兩個常用的宏:
宏定義含義
WIFEXITED(status)子進程正常退出時,返回一個非零值,否則返回零
WEXITSTATUS(status)當(dāng)WIFEXITED為真時,此宏才可用,返回該進程退出的代碼
示例,子進程調(diào)用exit(3)退出,WIFEXITED(status)指示子進程正常退出,WEXITSTATUS(status)就會返回3。get_status.c:
#include 《sys/types.h》
#include 《sys/wait.h》
#include 《stdio.h》
#include 《stdlib.h》
#include 《unistd.h》
int main(void)
{
int status;
pid_t pc, pr;
if((pc = fork()) 《 0)
{
printf(“error in fork!”);
exit(1);
}
else if(pc == 0)
{
printf(“This is child process with pid of %d.
”, getpid());
exit(3);
}
else
{
pr = wait(&status);
if(WIFEXITED(status))
{
printf(“the child process %d exit normally.
”, pr);
printf(“the return code is %d.
”, WEXITSTATUS(status));
}
else
printf(“the child process %d exit abnormally.
”, pr);
}
return 0;
}
assvf
$ 。/get_status
This is child process with pid of 2718.
the child process 2718 exit normally.
the return code is 3.
可以看出,父進程捕捉到了子進程的返回值3。
system()函數(shù)
函數(shù)原型:
#include 《stdlib.h》
int system(const char *cmdstring);
sysytem()調(diào)用fork()產(chǎn)生子進程,由子進程來調(diào)用/bin/sh-cmdstring來執(zhí)行參數(shù)cmdstring字符串所代表的命令,此命令執(zhí)行完后隨即返回原調(diào)用的進程。
編程示例,4次調(diào)用system,設(shè)置不同的命令行參數(shù),system返回不同的結(jié)果,cmd_system.c:
#include 《stdio.h》
#include 《stdlib.h》
int main(void)
{
int status;
if((status = system(NULL)) 《 0)
{
printf(“system error!
”);
exit(0);
}
printf(“exit status=%d
”, status);
if((status = system(“date”)) 《 0)
{
printf(“system error!
”);
exit(0);
}
printf(“exit status=%d
”, status);
if((status = system(“invalidcommand”)) 《 0)
{
printf(“system error!
”);
exit(0);
}
printf(“exit status=%d
”, status);
if((status = system(“who; exit 44”)) 《 0)
{
printf(“system error!
”);
exit(0);
}
printf(“exit status=%d
”, status);
return 0;
}
adss
$ 。/cmd_system
exit status=1
2019年 12月 10日 星期二 1436 CST
exit status=0
sh: 1: invalidcommand: not found
exit status=32512
deeplearning pts/0 2019-12-10 13:46 (192.168.1.110)
exit status=11264
第1次調(diào)用system,參數(shù)為NULL,返回結(jié)果為1,說明在本Linux系統(tǒng)下system可用;第2次調(diào)用system,參數(shù)為data,system成功執(zhí)行;第3次調(diào)用system,參數(shù)為一個非法的字符串命令,返回結(jié)果shell的終止狀態(tài)(命令出錯)32512;第4次調(diào)用system,參數(shù)為who,顯示登錄用戶情況,exit 44是退出當(dāng)前的shell,system成功返回,返回值11264。
參考:《精通Linux C編程》- 程國鋼
編輯:lyn
-
Linux
+關(guān)注
關(guān)注
87文章
11329瀏覽量
209971 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4344瀏覽量
62812 -
編譯
+關(guān)注
關(guān)注
0文章
661瀏覽量
32939
原文標題:Linux進程控制
文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論