每一個進程都由進程ID惟一標識, 而進程ID是獨一無二的.shell
#include <unistd.h>編程
pid_t getpid(void); // returns: 調用的進程id服務器
pid_t getppid(void); // returns: 調用進程的父id網絡
uid_t getuid(void); // returns: 調用進程的實際用戶(real user)id異步
uid_t geteuid(void); // returns: 調用進程的有效用戶(effective user)id函數
gid_t getgid(void); // returns: 調用進程的實際分組(real group)id學習
gid_t getegid(void); // returns: 調用進程的有效分組(effective group)idui
一個實際的例子:spa
#include <stdio.h> #include <unistd.h> int main( void ) { // 3824 printf("%d\n", getpid()); // 22432 printf("%d\n", getppid()); // 1000 printf("%d\n", getuid()); // 1000 printf("%d\n", geteuid()); // 1000 printf("%d\n", getgid()); // 1000 printf("%d\n", getegid()); return 0; }
調用fork函數可建立一個新進程.線程
#include <unistd.h>
pid_t fork(void);
returns: 子進程中返回0, 父進程中返回子進程id, 錯誤返回-1
fork函數特殊在於: 返回兩個值, 子進程返回0而父進程返回子進程id. 子進程是父進程的一個副本, 它將得到父進程相同的數據和堆棧.
#include <stdio.h> #include <unistd.h> int globvar = 6; char buf[] = "a write to stdout\n"; int main( void ) { int var; pid_t pid; var = 88; if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1) printf("write error"); printf("before fork\n"); if ((pid = fork()) < 0) { printf("fork error"); } else if (pid == 0) { globvar++; var++; } else { sleep(2); } printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var); return 0; }
終端輸出:
leicj@leicj:~/test$ ./a.out a write to stdout before fork pid = 4062, glob = 7, var = 89 pid = 4061, glob = 6, var = 88 leicj@leicj:~/test$ ./a.out > temp.out leicj@leicj:~/test$ cat temp.out a write to stdout before fork pid = 4066, glob = 7, var = 89 before fork pid = 4065, glob = 6, var = 88
重定向到終端, 緩衝區會被刷新; 重定向到文件, 則不會.
文件共享
考慮如下一個例子:
#include <stdio.h> #include <unistd.h> int main( void ) { FILE *file = fopen("temp", "w+"); pid_t pid; char buf1[] = "abcdef\n"; char buf2[] = "ABCDEF\n"; if (fputs(buf1, file) == EOF) { printf("write error\n"); } if ((pid = fork()) < 0) { printf("fork error\n"); } else if (pid == 0) { if (fputs("child:\n", file) == EOF) { printf("child write error\n"); } if (fputs(buf2, file) == EOF) { printf("child write error\n"); } } else { sleep(2); if (fputs("parent:\n", file) == EOF) { printf("parent write error\n"); } if (fputs(buf1, file) == EOF) { printf("write error\n"); } } return 0; }
終端輸出:
leicj@leicj:~/test$ ./a.out leicj@leicj:~/test$ cat temp abcdef child: ABCDEF abcdef parent: abcdef
這裏可能會有點疑惑是: 爲何"parent:"前面會有"abcdef"? 緣由也是由於寫入文件狀況下, 緩衝區並未被刷新, 因此第一個fputs會被執行一遍.
fork通常有兩個做用:
1. 父進程和子進程毫無相關, 執行fork後父進程就關閉描述符, 子進程也同樣. 例如在網絡編程中進程使用到, 一個服務器, 開多個客戶端進程.
2. 父進程等待子進程關閉(即一些處理須要子進程完成, 父進程才能繼續下去)
與fork函數有如下兩點不一樣:
1. vfork與fork同樣都建立一個子進程, 可是它並不將父進程的地址空間徹底複製到子進程中, 由於子進程會當即調用exec或exit, 因而也就不會訪問該地址空間.
2. vfork保證子進程先運行.
#include <stdio.h> #include <unistd.h> int globvar = 6; int main( void ) { int var; pid_t pid; var = 88; printf("before vfork\n"); if ((pid = vfork()) < 0) { printf("vfork error\n"); } else if (pid == 0) { globvar++; var++; _exit(0); } printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var); return 0; }
終端輸出:
leicj@leicj:~/test$ ./a.out before vfork pid = 4411, glob = 7, var = 89
五種正常的進程終止方式:
1. 在main函數中, 執行return函數.
2. 調用exit函數. 它會關閉全部的標準I/O流而且調用由atexit註冊的全部exit handlers.
3. 調用_exit或者_Exit. ISO C定義_Exit, 提供了不須要調用exit handler和信號的終止進程的方法. 它們均不會刷新緩衝區.
4. 進程中最後一個線程return回來.
5. 在最後一個線程中執行pthread_exit.
三種異常退出:
1. 調用abort, 它會生成SIGABRT信號.
2. 進程收到特殊的信號而終止.
3. 最後一個線程取消請求.
不管如何終止進程, 內核都會關閉全部打開的文件描述符, 釋放內存.
問題在於子進程的終止, 如何通知父進程. 對於三個exit函數(exit, _exit, _Exit), 它們都會將退出狀態返回給父進程. 在異常終止狀況下, 由內核生成一個終端狀態代表終止的緣由.
在調用fork函數時候, 會產生子進程, 但若是父進程先於子進程終止, 則默認init進程成爲子進程的父進程.
父進程會調用wait/waitpid來查看子進程是否已經終止. 若是子進程已經終止, 但父進程並未wait/waitpid它, 則子進程成爲僵死進程(子進程終止時依舊會保留一些信息, 例如進程號).
一個子進程終止(正常或者異常), 內核都會向父進程發送SIGGHLD信號. 不管是進程終止, 仍是信號的發送, 都是異步的. 因此父進程能夠忽略, 也能夠提供函數處理所接收到的信號(默認行爲是忽略).
而wait/waitpid會發生以下狀況:
1. 若是還有子進程在運行, 則阻塞.
2. 子進程終止時候當即返回.
3. 沒有任何子進程, 則返回錯誤.
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
returns: 成功返回進程id, 失敗返回-1, 返回0後面描述
而兩個函數的不一樣之處在於:
1. wait函數會一直阻塞直到子進程終止, 而waitpid提供參數用於阻止阻塞.
2. waitpid可控制等待的是哪一個子進程.
4. wait和waitpid函數
內核爲每一個終止子進程保存了必定量的信息,因此當終止進程的父進程調用wait或waitpid時,能夠獲得這些信息。這些信息至少包括進程ID,該進程的終止狀態,以及該進程使用的CPU時間總量。
當調用wait或waitpid的進程可能會發生以下狀況:
1) 若是其全部子進程都還在運行,則阻塞
2) 若是一個子進程已終止,正等待父進程獲取其終止狀態,則取得該子進程的終止狀態當即返回
3) 若是它沒有任何子進程,則當即出錯返回
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> int main( void ) { pid_t pid; int status; if ((pid = fork()) < 0) printf("fork error\n"); else if ( pid == 0 ){ printf("wait函數會阻塞,直到子進程終止,並捕捉子進程的終止信息!這樣也能夠保證子進程比父進程先執行。\n"); sleep( 2 ); } if ( wait(&status) != pid ) printf("wait error\n"); if ( pid > 0 ){ printf("main done\n"); } return 0; }
程序輸出:
waitpid的優點:
1) waitpid可等待一個特定的進程,而wait則返回任一終止子進程的狀態。
2) waitpid提供了一個wait的非阻塞版本。
3) waitpid支持做業控制
#include <sys/wait.h> #include <unistd.h> int main( void ) { pid_t pid1; pid_t pid2; if ( ( pid1 = fork()) < 0 ){ printf("fork error\n"); } else if ( pid1 == 0 ){ printf("fork 1....\n"); exit( 0 ); } if ( ( pid2 = fork() ) < 0 ){ printf("fork error\n"); } else if ( pid2 == 0 ){ printf("fork 2....\n"); exit( 0 ); } if ( waitpid( pid2, NULL, 0 ) != pid2 ){ printf("fork 2 exit error\n"); } else{ printf("fork 2 exit ok\n"); } if ( waitpid( pid1, NULL, 0 ) != pid1 ){ printf("fork 1 exit error\n"); } else{ printf("fork 1 exit ok\n"); } return 0; }
程序輸出:
5. 競爭條件
父進程/子進程之間的相互競爭:
#include <stdio.h> #include <sys/wait.h> static void charatatime( char * ); int main( void ) { pid_t pid; if ( ( pid = fork() ) < 0 ){ printf("fork error\n"); } else if ( pid == 0 ) charatatime("output from child\n"); else{ charatatime("output from parent\n"); } return 0; } static void charatatime( char *str ) { char *ptr; int c; setbuf( stdout, NULL ); for ( ptr = str; ( c = *ptr++ ) != 0; ) putc( c, stdout ); }
程序輸出有點亂:
6. exec函數
exec函數一般用於執行可執行文件,因此在fork後直接運行較好。咱們先編寫一個用於exec執行的程序echoall.c:
#include <stdio.h> int main( int argc, char *argv[] ) { int i; char **ptr; extern char **environ; for ( i = 0; i < argc; i++ ) printf("argv[%d]:%s\n", i, argv[ i ] ); for ( ptr = environ; *ptr != 0; ptr++ ) printf("%s\n", *ptr ); return 0; }
而後編譯以下:
cc -o echoall echoall.c
則echoall爲可執行文件,而後咱們就能夠編寫exec程序文件:
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> char *env_init[] = { "USER=unknown", "PATH=/tmp", NULL }; int main( void ) { pid_t pid; if ( ( pid = fork() ) < 0 ) printf("fork error\n"); else if ( pid == 0 ){ if ( execle( "/home/leichaojian/echoall","echoall","myarg1","MY ARG2", ( char * )0, env_init ) < 0 ) printf("execle error\n"); } if ( waitpid( pid, NULL, 0 ) < 0 ) printf("wait error\n"); if ( ( pid = fork() ) < 0 ) printf("fork error\n"); else if ( pid == 0 ){ if ( execlp( "/home/leichaojian/echoall","echoall","only 1 arg",( char * )0 ) < 0 ){ printf("execlp error\n"); } } return 0; }
其中/home/leichaojian能夠經過指令pwd來獲得。程序運行以下:
7. 解釋器文件
用一個程序來解釋腳本語言(PS:找段時間學習shell編程)
echoall.c:
#include <stdio.h> int main( int argc, char *argv[] ) { int i; char **ptr; extern char **environ; for ( i = 0; i < argc; i++ ) printf("argv[%d]:%s\n", i, argv[ i ] ); // for ( ptr = environ; *ptr != 0; ptr++ ) // printf("%s\n", *ptr ); return 0; }
而後咱們執行:
cc -o echoall echoall.c
shell.c:
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> int main( void ) { pid_t pid; if ( ( pid = fork() ) < 0 ) printf("fork error\n"); else if ( pid == 0 ){ if ( execl("/home/leichaojian/testinterp", "testinterp", "myarg1","MY ARG2",(char *)0 ) < 0 ) printf("execl error\n"); } if ( waitpid( pid, NULL, 0 ) < 0 ) printf("waitpid error\n"); return 0; }
而後程序輸出以下: