每一个进程都由进程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; }
而后程序输出以下: