Linux-創(chuàng)建進程與線程用到的函數(shù)解析
【1】exit:
- exit函數(shù)可以退出程序并將控制權(quán)返回給操作系統(tǒng),而用return語句可以從一個函數(shù)中返回給調(diào)用該函數(shù)的函數(shù)。如果在main()函數(shù)中加入return語句,那么在執(zhí)行這條語句后將退出main()函數(shù)并將控制權(quán)返回給操作系統(tǒng),這樣的一條return語句與exit函數(shù)作用是相同的。
下面通過一個程序測試以下在子函數(shù)中使用exit是否會直接在子函數(shù)中就把整個程序終止了:
exitExample.c
/*
測試exit函數(shù)與return函數(shù)的不同
*/
#include <stdio.h>
#include <stdlib.h>
void exitExample();
void exitExample()
{
int i = 0;
if(i==0)
exit(0);
}
int main()
{
// 調(diào)用一個函數(shù),測試其用exit是否還會返回到main函數(shù)
exitExample();
printf("看來在調(diào)用的函數(shù)中使用exit也能返回到調(diào)用這個函數(shù)的父函數(shù)來.\n");
return 0;
}
the result:
yangruo@Y700:~/workspace/processTrain/shiyan1$ ./exitExample
yangruo@Y700:~/workspace/processTrain/shiyan1$
發(fā)現(xiàn)確實是直接結(jié)束了,說明推測是正確的!
新的問題又來了:如果是子進程呢?會不會直接退出整個程序?
/*
*測試在子進程中使用exit,是否會直接退出整個程序
*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
void childFunc();
void childFunc()
{
printf("這是子進程,他的PID是%d, 他的父進程PID是%d.\n", getpid(), getppid());
exit(0);
}
int main()
{
printf("這是main函數(shù),也就是1號進程,他的PID是%d,他的父進程PID是%d.\n", getpid(), getppid());
// 創(chuàng)建一個子進程
pid_t child;
child = fork();
if(child == 0){
// 說明創(chuàng)建子進程成功
// 子進程執(zhí)行一個任務(wù)
childFunc();
}else if(child < 0){
puts("進程創(chuàng)建失敗\n");
exit(1);
}else if(child > 0){
printf("真的會輸出嗎?\n");
printf("如果輸出了的話就打印PID瞧瞧是哪個進程!\n");
printf("現(xiàn)在這個進程PID是%d.\n", getpid());
}
/*
發(fā)現(xiàn)一個有趣的問題:若直接是else,而不是else if(child < 0),那么else里面的語句依舊會被輸出,后來找到原因:
這是邏輯有問題,直接else的話,程序不知道這是在說child的其他值。
?。?!
NO,測試了else if(child > 0),發(fā)現(xiàn)里面的語句還是會輸出!所以這并不是邏輯出錯了,if else語句是知道指的是child
這個變量的!
哦哦!原來fork()是個神奇的函數(shù)!他僅僅被調(diào)用了一次,卻能返回兩次,更多見注釋【2】fork
*/
// 父進程等待子進程退出
pid_t cpid = wait(NULL); // 【5】
printf("子進程%d已經(jīng)退出.\n", cpid);
// 父進程自己退出
printf("父進程%d退出,程序終止\n", getpid());
return 0;
}
- 現(xiàn)在說說exit函數(shù)的返回值,即exit(int status)的status是什么意思:
- “C 語言的設(shè)計之初就是為 Unix 系統(tǒng)設(shè)計的,而這個系統(tǒng)是『很多程序互相配合』搭配成一個系統(tǒng)。每個運行著的程序都是進程,而進程就會有父進程,父進程通常是直接啟動你的進程,父進程死亡的進程會被 init 收養(yǎng),其父進程變?yōu)?init,而 init 的父進程是進程 0,進程 0 則是系統(tǒng)啟動時啟動的第一個進程?!薄?em>引用自知乎用戶pansz】
- exit()里的參數(shù),是傳遞給父進程的。對父進程來說,你的進程仿佛是一個函數(shù),而函數(shù)可以有返回值。通常一個程序的父進程可能是任何進程,因此我們無法語氣我們的父進程是否規(guī)定必須要有這個返回值,那么我們應(yīng)當提供這個返回值,以保證不同的父進程的需求得到滿足。
- 返回值“0”表示沒有錯誤,程序已經(jīng)正常執(zhí)行完畢,非0值表示有錯誤發(fā)生,至于非0值具體為多少由開發(fā)者自己定義,比如1表示輸入錯誤,2表示計算錯誤之類,也有一些是由系統(tǒng)定義的錯誤代碼,比如棧溢出,除零之類的錯誤。
- 為什么要使用exit呢?
是歷史原因,雖然現(xiàn)在大多數(shù)平臺下,直接在 main() 函數(shù)里面 return 可以退出程序。但是在某些平臺下,在 main() 函數(shù)里面 return 會導(dǎo)致程序永遠不退出(因為代碼已經(jīng)執(zhí)行完畢,程序卻還沒有收到要退出的指令)。換句話說,為了兼容性考慮,在特定的平臺下,程序最后一行必須使用 exit() 才能正常退出,這是 exit() 存在的重要價值。
【2】fork
- 一個進程,包括代碼、數(shù)據(jù)、分配給進程的資源。fork()通過系統(tǒng)調(diào)用創(chuàng)建一個與原來進程幾乎完全相同的進程,也就是兩個進程可以做完全相同的事,但如果初始參數(shù)或者傳入的變量不同,兩個進程也可以做不同的事。
- 一個進程調(diào)用fork()后,系統(tǒng)先給新的進程分配資源,例如存儲數(shù)據(jù)和代碼的空間。然后把原來的進程的所有值都復(fù)制到新的進程中,只有少數(shù)值和原來的進程不同,相當于克隆了一個自己。
- fork()調(diào)用的奇妙之處在于它僅僅被調(diào)用一次,卻能夠返回兩次,它有可能有三種不同返回值:
- 在父進程中,fork返回新創(chuàng)建子進程的進程PID;
- 在子進程中,fork返回0;
- 如果出現(xiàn)錯誤,fork返回一個負值。
在fork函數(shù)執(zhí)行完畢后,如果創(chuàng)建新進程成功,則出現(xiàn)兩個進程,一個是子進程,一個是父進程。在子進程中,fork返回0,父進程返回新創(chuàng)建子進程的進程ID。
用Strace命令跟蹤了一個簡單的 fork 程序,發(fā)現(xiàn) fork 其實調(diào)用的是clone函數(shù):
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fa8e369f9d0) = 3252
【3】pthread_create()第二個參數(shù)
pthread_create()的第二個參數(shù)是設(shè)置線程的屬性,這些屬性主要包括綁定屬性(SCS、PCS)、分離屬性、堆棧地址、堆棧大小、優(yōu)先級。其中系統(tǒng)默認是非綁定、非分離、缺省1M的堆棧、與父進程同樣級別的優(yōu)先級。若把第二個參數(shù)設(shè)置為NULL的話,將采用系統(tǒng)默認的屬性配置。
- 綁定屬性
在Linux中,采用的是“一對一”的線程機制,即一個用戶線程對應(yīng)一個內(nèi)核線程。綁定屬性就是指一個用戶線程固定地分配給一個內(nèi)核線程,因為CPU時間片的調(diào)度是面向內(nèi)核線程(輕量級進程)的,因此具有綁定熟悉的線程可以保證在需要的時候總有一個內(nèi)核線程與之對應(yīng),而與之對應(yīng)的非綁定屬性就是指用戶線程和內(nèi)核線程的關(guān)系不是始終固定的,而是由系統(tǒng)來分配。 - 分離屬性
分離屬性是決定以一個什么樣的方式來終止自己。在非分離狀況下,當一個線程結(jié)束時,它多占用的線程沒有得到釋放,也就是沒有真正的終止,需要通過pthread_join()來釋放資源。而在分離屬性下,一個線程結(jié)束就會立即釋放它所占有的系統(tǒng)資源。但這里有一點要注意的是,如果設(shè)置一個線程分離屬性,而這個線程又運行得非??斓脑?,那么它可能在pthread_create函數(shù)返回之前就終止了線程函數(shù)的運行,它終止以后就很可能將線程號和系統(tǒng)資源移交給其他的線程使用,這時調(diào)用pthread_create的線程就得到錯誤的線程號。
上面的屬性都是通過一些函數(shù)完成的,通常先調(diào)用pthread_attr_init來初始化,之后調(diào)用相應(yīng)的屬性設(shè)置函數(shù)。
- pthread_attr_init
功能:對線程數(shù)形變量的初始化
頭文件:<pthread.h>
函數(shù)原型:int pthread_attr_init(pthread_attr_t *arr);
函數(shù)傳入值:attr(線程屬性)
函數(shù)返回值:成功返回0,失敗返回-1 - pthread_attr_setscope
功能:設(shè)置線程綁定屬性
頭文件:<pthread.h>
函數(shù)原型:int pthread_attr_setscope(pthread_attr_t *attr, int scope);
函數(shù)傳入值:attr(線程屬性);scope:PTHREAD_SCOPE_SYSTEM(綁定),PTHREAD_SCOPE_PROCESS(非綁定)
函數(shù)返回值:同1 - pthread_attr_setdetachstate
功能:設(shè)置線程分離屬性
頭文件:<pthread.h>
函數(shù)原型:int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
函數(shù)傳入值:attr(線程屬性),detachstate:PTHREAD_CREATE_DETACHED(分離),PTHREAD_CREATE_JOINABLE(非分離)
函數(shù)返回值:同1 - pthread_attr_getschedparam
功能: 得到線程優(yōu)先級。
頭文件: <pthread.h>
函數(shù)原型: int pthread_attr_getschedparam (pthread_attr_t* attr, struct sched_param* param);
函數(shù)傳入值:attr:線程屬性;
param:線程優(yōu)先級;
函數(shù)返回值:同1 - pthread_attr_setschedparam
功能: 設(shè)置線程優(yōu)先級。
頭文件: <pthread.h>
函數(shù)原型: int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
函數(shù)傳入值:attr:線程屬性。
param:線程優(yōu)先級。
函數(shù)返回值:同1
pthAttrExa.c
/*測試線程屬性*/
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *pthread_func_1();
void *pthread_func_2();
void *pthread_func_1()
{
int i = 0;
for(; i<6; i++)
{
printf("This is pthread_1.\n");
if(i == 2)
{
pthread_exit(0);
}
}
}
void *pthread_func_2()
{
int i = 0;
for(; i<3; i++)
{
printf("This is pthread_2.\n");
}
}
int main()
{
pthread_t pt_1 = 0;
pthread_t pt_2 = 0;
pthread_attr_t attr = {0};
int ret = 0;
pthread_attr_init(&attr); // 屬社設(shè)置
//pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&pt_1, &attr, pthread_func_1, NULL);
if(ret != 0)
{
printf("創(chuàng)建線程失敗\n");
}
ret = pthread_create(&pt_2, NULL, pthread_func_2, NULL);
if(ret != 0)
{
printf("創(chuàng)建線程失敗\n");
}
int ret2;
ret2 = pthread_join(pt_2, NULL);
if(ret2 == 0)
printf("線程2已經(jīng)結(jié)束\n");
else
printf("線程2沒有結(jié)束,錯誤號%d", ret2);
int ret1;
ret1 = pthread_join(pt_1, NULL);
if(ret1 == 0)
printf("線程1已經(jīng)結(jié)束\n");
else
printf("線程1沒有結(jié)束,錯誤號%d\n", ret1);
return 0;
}
the result:
$ ./pthAttrExaThis is pthread_1.
This is pthread_1.
This is pthread_1.
This is pthread_2.
This is pthread_2.
This is pthread_2.
線程2已經(jīng)結(jié)束
線程1沒有結(jié)束,錯誤號22
【4】pthread_join()
函數(shù)原型:int pthread_join(pthread_t thread, void **retval);
頭文件:<pthread.h>
參數(shù):
- thread:線程標識符,即線程ID
- retval:用戶定義的指針,用來存儲被等待線程的返回值
返回值:0代表成功,失敗返回錯誤號
功能:以阻塞的方式等待thread指定的線程結(jié)束,當函數(shù)返回時,被等待線程的資源被收回。如果進程已經(jīng)結(jié)束,那么該函數(shù)會立即返回,并且thread指定的線程必須是JOINABLE的。
注意:
- 一個可“join”的線程所占用的內(nèi)存僅當有線程執(zhí)行pthread_join()后才會釋放,因此為了避免內(nèi)存泄露,所有線程終止時,要么已被設(shè)為DETACHED,要么用pthread_join()來回收資源。
- 一個線程不能被多個線程等待,否則第一個接收到信號的線程成功返回,其余調(diào)用pthread_join()的線程返回錯誤代碼ESRCH。
【4`】pthread_exit()
函數(shù)原型:void pthread_exit(void *retval);
頭文件:<pthread.h>
參數(shù):
retval:pthread_exit()調(diào)用線程的返回值,可以用pthread_join()函數(shù)來檢索獲取。(只有當線程狀態(tài)是非分離屬性時才能正常得到這個值)
功能:退出線程。
注意:
- 線程終止最重要的問題是資源釋放的問題,特別是臨界資源的釋放。因為臨界資源在一段時間內(nèi)只能被一個線程所持有,當線程要使用臨界資源時需提交請求,如果該資源未被使用則申請成功,否則等待。臨界資源使用完畢后要釋放以便其他線程可以使用。
- 線程終止的另外一個問題是線程間的同步問題。一般情況下,進程中各個線程的運行是相互獨立的,線程的終止并不會相互通知,也不會影響其他的線程,終止的線程所占用的資源不會隨著線程的終止而歸還系統(tǒng),而是仍為線程所在的進程持有。正如進程之間可以使用wait()系統(tǒng)調(diào)用來等待其他進程結(jié)束一樣,線程也有類似的函數(shù):pthread_join()。
ptrExitJoin.c
#include<stdio.h>
#include<pthread.h>
void assisthread(void*arg)
{
printf("I am helping to do some jods\n");
//sleep(3);
printf("pthreadID:%lu\n",pthread_self());
//pthread_exit(0);
}
int main()
{
pthread_t assistthid;
int status = 0;
// pthread_attr_t attr = {0};
// pthread_attr_init(&attr); // 屬社設(shè)置
// pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
printf("main pthreadID:%lu\n",pthread_self());
// pthread_create(&assistthid,&attr,(void*)assisthread,NULL);
pthread_create(&assistthid,NULL,(void*)assisthread,NULL);
printf("create pthreadID:%lu\n",assistthid);
pthread_join(assistthid,(void*)&status);//等待線程assisthread結(jié)束
printf("assistthid's exit is caused %d\n",status);
return 0;
}
【5】wait
頭文件:<sys/types.h> <sys/wait.h>
函數(shù)原型:pid_t wait(int *status)
函數(shù)說明:wait()會暫時停止目前進程的執(zhí)行, 直到有信號來到或子進程結(jié)束. 如果在調(diào)用wait()時子進程已經(jīng)結(jié)束, 則wait()會立即返回子進程結(jié)束狀態(tài)值. 子進程的結(jié)束狀態(tài)值會由參數(shù)status 返回, 而子進程的進程識別碼也會一快返回. 如果不在意結(jié)束狀態(tài)值, 則參數(shù) status 可以設(shè)成NULL. 子進程的結(jié)束狀態(tài)值請參考waitpid().
傳入值:status是一個整型變量指針,是該子進程退出的狀態(tài)。若status不為NULL,則通過它可以獲得子進程的結(jié)束狀態(tài)。另外,子進程的結(jié)束狀態(tài)可由Linux中一些特殊的宏來測試。
返回值:如果執(zhí)行成功則返回子進程識別碼(PID),如果有錯誤返回-1,失敗原因在errno中。