進程即一個程序的動態執行。引用apue上的一句話:"A thorough understanding of the UNIX System's process control is essential for advanced programming".一.總述 1.進程的開始 在C語言中,進程是由一個main函數開始。
int main(int argc,char *argv[])我們可以向程序傳入參數,以字符串數組的形式存儲在argv,同時argc記錄傳入參數的個數。而且還可以在main的第三個參數中傳入環境變量的數組,但一般不這樣做。用全局environ更好。 2.進程中 在一個正在執行的進程中,可以fork出子進程(copy 出一個進程),也可以vfork出子進程(與新的進程share地址空間),同時父進程需要wait子進程,否則子進程就會變成僵屍進程,無家可歸。我們還可以用exec function進行替換程序(但進程ID不變)。在一個進程中,system()可以執行一個shell命令,但要注意system對set-user-ID會造成權限的傳遞,這一點在後面會詳細敘述。 3.進程結束 進程的結束有很多種方式,大致可以分為正常結束與異常結束。具體的我就不贅述了。主要解釋exit,_exit,_Exit的區別。_exit,_Exit在結束的時候不會fllush緩沖,而exit在結束時會fllush緩沖,而且在某些UNIX系統中,exit還會關閉標准I/O流,以及與標准I/O流相關聯的STDOUT_FILENO與STDIN_FILENO文件描述符。在這樣的情況下,只需要用dup先復制文件描述符就可以了。二.system call 以及 一些知識點 我喜歡用函數來總結知識點。 1.atexit()。可以使用atexit()來注冊若干個exit handler,參數是函數的地址。當進程結束時,就會執行每一個注冊了的exit handler,即執行函數。但要注意,這個執行順序是棧的順序,也就是"先注冊,後執行"。比如說,當我做完一件事(即exit時),我先注冊的A執行funcA_1,再注冊的B執行funcB_2,最後注冊的A執行funcA_3。但最後的執行順序卻是反的。程序如下:
1 #include <apue.h> 2 3 static void funcA_1() 4 { 5 printf("funcA_1\n"); 6 } 7 static void funcB_2() 8 { 9 printf("funcB_2\n"); 10 } 11 static void funcA_3() 12 { 13 printf("funcA_3\n"); 14 } 15 16 int main() 17 { 18 if(atexit(funcA_1)!=0) 19 err_sys("I can't register funcA_1"); 20 if(atexit(funcB_2)!=0) 21 err_sys("I can't register funcB_2"); 22 if(atexit(funcA_3)!=0) 23 err_sys("I can't register funcA_3"); 24 exit(0); 25 }執行結果如下:?
funcA_3
funcB_2
funcA_1
2.一個C程序在內存中主要分為4個部分: a.初始化了的數據區域。比如int a=1;且a是在函數之外定義的,那麼a就是存儲在這裡的。這裡的a即是C語言中的靜態全局變量。 b.非初始化數據區域。比如int b;且b是定義在所有函數之外,那麼b就存儲在此,即未初始化的靜態全局變量,會被初始化為0或者null. c.棧。存儲自動變量,也就是在函數之內聲明的變量。還有call stack,很熟悉的東西。 d.堆。動態內存分配。malloc盡職盡責。 3.每個進程都有一個唯一的ID。getpid()可以得到本進程的process ID,getppid()可以得到父進程的ID,但無法得到子進程的ID,因為一個進程的父進程是唯一的,但可以有多個子進程。Process ID 0是系統進程的ID,process ID 1是init進程的ID,當一個父進程先於子進程結束時,子進程就會被init進程所繼承,那麼該子進程的父進程就變成了init.這一點會在後面與僵屍進程作比較。 4.fork().這是進程控制中很核心的system call. fork產生一個新的進程,這個進程除了代碼與父進程共享(代碼段在執行階段是不會發生變化的)外,其他的都是父進程的一份copy(包括分配的內存,等等),除了繼承關系外,與父進程就沒什麼關系了。而vfork是產生一個與父進程共享一切的子進程。 5.wait,waitpid.在子進程exit後,需要wait才能使得子進程不變成僵屍進程。如果子進程exit後,父進程沒有在wait,那麼這個子進程就會變成一個僵屍進程。而子進程被init所繼承的原因是其父進程提前結束,即使這個父進程沒有wait也不會造成僵屍進程,因為它已經提前結束了而子進程還沒有exit,所以子進程會被init所繼承了。由此也可說明init進程是在wait它的所有子進程的。下面是一個形成僵屍進程的程序: 1 #include <apue.h> 2 3 int main() 4 { 5 pid_t pid; 6 if( (pid=fork())<0 ) 7 err_sys("error fork"); 8 else if(pid==0) //child 9 exit(1); 10 11 sleep(2); 12 if(system("ps -o pid,ppid,state,tty,command")<0) 13 err_sys("system() error"); 14 15 exit(0); 16 }以下是結果:?
3435 3434 Z pts/0 [test] <defunct>
這裡的Z就表示這是一個僵屍進程了。 6.race condition.當fork後生成子進程後,我們並不知道是子進程先執行還是父進程先執行,這時就會產生“race”。而vfork會確保子進程先於父進程執行。 7.exec function.這是一組函數。exec有些像替換程序的意思。當執行exec後,當前進程的代碼就被exec所要執行的程序代碼所替換,換句話說,就是當前進程在exec之後的語句都無效了。 8.system(). system("data>file"),即執行一個shell命令。我很喜歡這個函數,我覺得這個函數可以很輕易就實現與shell的簡單交互,在某些地方會非常有用。但要注意的是,system可能會引起一個權限的傳遞。比如,program1有在root權限下設置的set-user-ID,這就意味著任何用戶在執行program1時都會具有superuser permission.那麼,如果在program1中調用system("program2"),就會將program1的superuser permission傳遞給program2,而這顯然是我們不願意看到的。三.我看書時的一些筆記與想法(有些摘自apue) 1. Three functions terminate a program normally:_exit and _Exit,which return to the kernel immediately,and exit,which performs certain cleanup processing and then returns to the kernel. 2.Returning an integer value from the main function is equivalent to calling exit with the same value.Thus exit(0) is the same as return (0) from the main function. 3.Note that the only way a program is executed by the kernel is when one of the exec functions is called. 4.Stack,where automatic variables are stored,along with information that is saved each time a function is called. 5.We can affect the environment of only the current process and any child processed that we invoke.We cannot affect the environment of the parent process,which is often a shell. 6.*Process ID 0* is usually the scheduler process and is often known as the swapper.No program on disk corresponds to this process,which is part of the kernel and is known as a *system process*. 7.*Process ID 1* is usually the *init* process and is invoked by the kernel at the end of the bootstrap procedure. It is a normal user process,not a system process within the kernel ,like the swapper,although it does run with superuser privileges. 8.fork() function is called once but returns twice. 9.*For example,the child process gets a copy of the parent's data space,heap,and stack.Note that this is a copy for the child share the text segment. 10.注意,如果char buf[100],這是一個固定大小的數組,那麼sizeof()就會返回其字節數100; 如果:char *str=(char *)malloc(100*sizeof(char));那麼sizeof(str)就是返回指針的大小。而strlen是返回當前不為'\0'的字節數。sizeof(buf)得到的字節數是要包括'\0'的 .strlen不會包括'\0'. 11.The standard output is line buffered if it's connected to a terminal device;otherwise,it's fully buffered. 12.Indeed,one characteristic of fork is that all file descriptors that are open in the parent are duplicated in the child. 13.*file table 中包含了current file offset,所以,共享一個file table就相當於共享current file offset.這很重要* 14.Regardless of how a process terminates,the same code in the kernel is eventually executed.This kernel code closes all the open descriptors for the process,releases the memory that it was using,and the like. 15.在進程異常終止後,是內核,而不是進程本身來生成 結束狀態 。 16.當一個進程結束的時候,不管是正常結束還是異常終止,內核都會通知它的父進程(發送SIGCHLD信號給父進程)。 17.wait,waitpid的一個作用就是父進程用來等待子進程,防止子進程變成僵屍進程。僵屍進程,就是在一個子進程結束(*也就是exit()。一定要注意是exit()後再去找wait()*)的時候,沒有父進程在wait,那麼這個進程就變成了僵屍進程。 18.With wait(),the only real error is if the calling process has no children.(Another error return is possible,in case the function call is interrupted by a signal)。 19.With fork,we can create new processes;and with the exec functions,we can initiate new programs. 20.ARG_MAX is the total size of the argument list and the environment list.This value must be at least 4096 bytes on a POSIX.1 system. 21.file descriptors的close-on-exec flag的作用是什麼? Every open file descriptor in a process has a close-on-exec.If this flag is set ,the descriptor is closed across an exec() function.Otherwise,the descriptor is left open across the exec function. 22.Only a superuser process can change the real user ID. 23.使用exec function後,該程序所在的進程就被exec所指定的程序給替換了,原程序中exec function後面的語句就不會執行了。如果那些語句還執行了,就說明exec function的執行出現了錯誤。 24.在exec function中可以使用./ 當前路徑 比如:execl("./hello"......)。 25.注意 execl("./hello",arg0,arg1,...,(char *)0);這裡的arg0是該命令行的第0個參數。一定要注意,這裡很容易將arg0當成該命令行的第1個參數,因為在執行命令行的時候是把程序名稱當作第0個參數的。這裡很容易出錯. 我覺得在使用execl的時候最好將arg0賦一個不需要的值然後棄用arg0,從arg1開始用,這樣與平常使用命令行的思路才一樣,更不容易出錯。 我覺得這與a[100],從a[1]開始使用是同樣的思想。當然這也不是必需的。比如./hello是一個shell,那麼就需要傳入類似"sh"這樣的arg0參數。視情況而定。 26.What happens if we call system() from a set-user-ID program? Doing so is a security hole and should never be done.The system() function should never be used from a set-user-ID or a set-group-ID program. 27.A new process record is *initialized* by the kernel for the child after a fork(),not when a new program is executed.Each accounting record is written when the process *terminates* . 28.scanf,printf,在標准輸入輸出流被關閉的情況下會返回-1。 29.uid=0 即root 參考資料:apue 如果您覺得我的文章對您有幫助,請贊一下,非常感謝! 本文出自 “Neil的博客” 博客,請務必保留此出處http://neilhappy.blog.51cto.com/5504414/1093800