linux進程管理(C語言),linux進程管理c語言
理解進程式控制制的原理對於理解和修改fio project非常的重要。"fio is an I/O tool meant to be used both for benchmark and stress/hardware verification."
進程
unix提供了大量的從c程式中作業系統的系統調用(別的語言應該也是有的吧)。
建立進程
每一個進程都有一個正數的id,叫做pid。getpid函數返回調用進程的pid,getppid函數返回它的父進程的pid。
fork
exit
父進程和子進程是並發啟動並執行獨立進程。核心能夠以任意方式交替執行他們的邏輯控制流程中的指令。
如果能夠在fork函數在父進程和子進程中返回後立即暫停這兩個進程,我們會看到每個進程的地址空間都是相同的。(每個進程有相同的使用者棧,相同的本地變數值,相同的堆,相同的全域變數值以及相同的代碼。然而他們都有自己獨立的地址空間)
關於這個,fio這個程式利用到它的地方就是
while (todo) { struct thread_data *map[REAL_MAX_JOBS]; struct timeval this_start; int this_jobs = 0, left; for_each_td(td, i) { if (td->runstate != TD_NOT_CREATED) continue; if (td->o.start_delay) { spent = utime_since_genesis(); if (td->o.start_delay > spent) continue; } if (td->o.stonewall && (nr_started || nr_running)) { dprint(FD_PROCESS, "%s: stonewall wait\n", td->o.name); break; } init_disk_util(td); td->rusage_sem = fio_mutex_init(FIO_MUTEX_LOCKED); td->update_rusage = 0; /* * Set state to created. Thread will transition * to TD_INITIALIZED when it's done setting up. */ td_set_runstate(td, TD_CREATED); map[this_jobs++] = td; nr_started++; ... if (td->o.use_thread) { int ret; dprint(FD_PROCESS, "will pthread_create\n"); ret = pthread_create(&td->thread, NULL, thread_main, td); if (ret) { log_err("pthread_create: %s\n", strerror(ret)); nr_started--; break; } ret = pthread_detach(td->thread); if (ret) log_err("pthread_detach: %s", strerror(ret)); } else { pid_t pid; dprint(FD_PROCESS, "will fork\n"); pid = fork(); if (!pid) { int ret = fork_main(shm_id, i); _exit(ret); } else if (i == fio_debug_jobno) *fio_debug_jobp = pid; } |
|
這就相當於這麼編程:
#include <stdio.h> int main() { int i=1; int pid; while((i--)>=0){ pid=fork(); if(pid==0){ i--; printf("in the child process.\n"); } else printf("in the parent process.\n"); } } |
編譯並執行上面那段程式的結果:
root@localhost ~]# ./a.out in the parent process. in the child process. in the parent process. in the child process. |
一共建立了兩個進程,只不過在Fio中的子進程的執行是由另外一個函數fork_main和thread_main來決定的。
note:thread main這個函數做了許多事情,會再分析。
回收子進程
當一個進程由於某種原因終止時,核心並不是立即把它從系統中清除。相反,進程被保持在一種已終止的狀態中,直到被它的父進程reap。當父進程回收已經終止的子進程時,核心將子進程的退出狀態(這是什麼樣的退出狀態呢,留個心)傳遞給父進程,然後拋棄已終止的進程,從此時開始,該進程就不存在了。
一個終止了但未被回收的進程稱為殭屍zombie。
如果父進程沒有回收他的zombie就終止了,那麼核心就會安排init進程來回收他們。長時間啟動並執行程式,比如shell或者伺服器,總是應該回收他們的zombie(總是在消耗系統的儲存空間資源)。
一個進程可以通過調用waitpid函數來等待它的子進程終止或者停止。
函數原型:
#include<sys/types.h> #include<sys/wait.h> 定義函數 pid_t waitpid(pid_t pid,int * status,int options); waitpid - 函數說明 waitpid()會暫時停止目前進程的執行,直到有訊號來到或子進程
結束。如果在調用 waitpid()時子進程已經結束,則 waitpid()會立即
返回子進程結束狀態值。 子進程的結束狀態值會由參數 status 返回,
而子進程的進程識別碼也會一起返回。如果不在意結束狀態值,則
參數 status 可以設成 NULL。參數 pid 為欲等待的子進程識別碼,
其他數值意義如下:
pid<-1 等待進程組識別碼為 pid 絕對值的任何子進程。
pid=-1 等待任何子進程,相當於 wait()。
pid=0 等待進程組識別碼與目前進程相同的任何子進程。
pid>0 等待任何子進程識別碼為 pid 的子進程。
參數options提供了一些額外的選項來控制waitpid,參數 option 可以為 0 或可以用"|"運算子把它們串連起來使用,比如:
ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
如果我們不想使用它們,也可以把options設為0,如:
ret=waitpid(-1,NULL,0);
WNOHANG 若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待。若結束,則返回該子進程的ID。
WUNTRACED 若子進程進入暫停狀態,則馬上返回,但子進程的結束狀態不予以理會。WIFSTOPPED(status)宏確定返回值是否對應與一個暫停子進程。
子進程的結束狀態返回後存於 status,底下有幾個宏可判別結束情況:
WIFEXITED(status)如果若為正常結束子進程返回的狀態,則為真;對於這種情況可執行WEXITSTATUS(status),取子進程傳給exit或_eixt的低8位。
WEXITSTATUS(status)取得子進程 exit()返回的結束代碼,一般會先用 WIFEXITED 來判斷是否正常結束才能使用此宏。
WIFSIGNALED(status)若為異常結束子進程返回的狀態,則為真;對於這種情況可執行WTERMSIG(status),取使子進程結束的訊號編號。
WTERMSIG(status) 取得子進程因訊號而中止的訊號代碼,一般會先用 WIFSIGNALED 來判斷後才使用此宏。
WIFSTOPPED(status) 若為當前暫停子進程返回的狀態,則為真;對於這種情況可執行WSTOPSIG(status),取使子進程暫停訊號編號。
WSTOPSIG(status) 取得引發子進程暫停訊號代碼,一般會先用 WIFSTOPPED 來判斷後才使用此宏。
如果執行成功則返回子進程識別碼(PID) ,如果有錯誤發生則返回
返回值-1。失敗原因存於 errno 中。 |
fio使用waitpid的例子
* Run over the job map and reap the threads that have exited, if any. */ static void reap_threads(unsigned int *nr_running, unsigned int *t_rate, unsigned int *m_rate) { ... realthreads = pending = cputhreads = 0; for_each_td(td, i) { int flags = 0; /* * ->io_ops is NULL for a thread that has closed its * io engine */ if (td->io_ops && !strcmp(td->io_ops->name, "cpuio")) cputhreads++; else realthreads++; ... flags = WNOHANG; if (td->runstate == TD_EXITED) flags = 0; /* * check if someone quit or got killed in an unusual way */ ret = waitpid(td->pid, &status, flags); if (ret < 0) { if (errno == ECHILD) { log_err("fio: pid=%d disappeared %d\n", (int) td->pid, td->runstate); td->sig = ECHILD; td_set_runstate(td, TD_REAPED); goto reaped; } perror("waitpid"); } else if (ret == td->pid) { if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); if (sig != SIGTERM && sig != SIGUSR2) log_err("fio: pid=%d, got signal=%d\n", (int) td->pid, sig); td->sig = sig; td_set_runstate(td, TD_REAPED); goto reaped; } if (WIFEXITED(status)) { if (WEXITSTATUS(status) && !td->error) td->error = WEXITSTATUS(status); td_set_runstate(td, TD_REAPED); goto reaped; } } ... |
讓進程休眠
sleep函數將一個進程掛起一段指定的時間。sleep函數返回0,否則返回剩下的要休眠的秒數(是可能的,因為可能被訊號中斷而過早的返回)。
另外一個有用的函數是pause,該函數讓調用函數休眠,直到該進程收到一個訊號。
另外,如果想要睡眠一段更加精確的時間,可以用usleep函數,usleep函數能把進程掛起一段時間, 單位是微秒(千分之一毫秒)。
進程間的訊號傳遞note:由於時間關係,有空再補充。
參考文獻
Caapp:深入理解電腦系統
源碼fio-2.1.10