赞
踩
本节内容主要是介绍linux/unix进程API的使用;getpid,fork,exit,atexit,abort,wait/waitpid。
一:进程终止
有8种方式使得进程终止,其中5种为正常终止,它们是:
1.从main函数返回
2.调用exit函数
3.调用_exit或_Exit函数
4.进程的最后一个线程从启动例程返回
5.进程的最后一个线程调用pthread_exit返回
3种异常终止方式是
6.调用abort函数终止
7.收到一个信号终止
8.进程的最后一个线程对取消请求做出响应
进程终止函数:
#include<stdlib.h>
void exit(int status);
void _Exit(int status);
#include<unistd.h>
void _exit(int status);
exit使进程退出时,会自动调用预先注册的atexit函数,
#include<stdlib.h>
int atexit(void (*func)(void)); //注意func被调用的顺序跟预先注册的顺序相反
一个例子:
- #include "apue.h"
-
- static void my_exit1(void);
- static void my_exit2(void);
-
- int
- main(void)
- {
- if (atexit(my_exit2) != 0)
- err_sys("can't register my_exit2");
-
- if (atexit(my_exit1) != 0)
- err_sys("can't register my_exit1");
- if (atexit(my_exit1) != 0)
- err_sys("can't register my_exit1");
-
- printf("main is done\n");
- return(0);
- }
-
- static void
- my_exit1(void)
- {
- printf("first exit handler\n");
- }
-
- static void
- my_exit2(void)
- {
- printf("second exit handler\n");
- }
运行结果,注意看my_exit1跟myexit2函数的执行顺序:
二:进程创建:
进程ID:
#include<unistd.h>
pid_t getpid(void); //返回该进程的进程ID
pid_t getppid(void);//返回该进程的父进程ID
uid_t getuid(void);//返回该进程的用户ID
uid_t geteuid(void);//返回该进程的有效用户ID
gid_t getgid(void);//返回该进程的组ID
gid_t getegid(void);//返回该进程的有效组ID
创建进程:
pid_t fork(void); //在子进程中,函数返回值为0,父进程中返回值为子进程的进程ID
子进程会获得父进程的进程空间,文件描述符,缓冲区(如果缓冲区没有冲洗的话)等所有数据的一个副本,一个例子:
- #include "apue.h"
-
- int globvar = 6; /* external variable in initialized data */
- char buf[] = "a write to stdout\n";
-
- int
- main(void)
- {
- int var; /* automatic variable on the stack */
- pid_t pid;
-
- var = 88;
- if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
- err_sys("write error");
- printf("before fork\n"); /* we don't flush stdout */
-
- if ((pid = fork()) < 0) {
- err_sys("fork error");
- } else if (pid == 0) { /* child */
- globvar++; /* modify variables */
- var++;
- } else {
- sleep(2); /* parent */
- }
-
- printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
- var);
- exit(0);
- }
输出结果:可以看到父子进程各有一个glob,var值,他们是各自独立的。另外当输出重定向到文件后,缓冲区没有被换行符冲洗,子进程会复制父进程的缓冲区,before fork 输出2次。(如果把before fork后的换行符去掉,before fork在终端也会输出2次)
不同之处:子进程不继承父进程的文件锁,子进程的未处理闹钟(alarm函数产生)被清除,子进程的未处理阻塞信号集被清除。
创建进程vfork:
pid_t vfork(void); //在子进程中,函数返回值为0,父进程中返回值为子进程的进程ID
跟fork不同的是,vfork不复制父进程的进程空间,而是直接在父进程的进程空间中运行,其二,父进程会阻塞直到vfork的子进程调用exit或exec函数后才执行。
- #include "apue.h"
-
- int globvar = 6; /* external variable in initialized data */
-
- int
- main(void)
- {
- int var; /* automatic variable on the stack */
- pid_t pid;
-
- var = 88;
- printf("before vfork\n"); /* we don't flush stdio */
- if ((pid = vfork()) < 0) {
- err_sys("vfork error");
- } else if (pid == 0) { /* child */
- globvar++; /* modify parent's variables */
- var++;
- _exit(0); /* child terminates */
- }
-
- /* parent continues here */
- printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
- var);
- exit(0);
- }
输出:可以看到子进程改变了父进程空间的变量
等待子进程的终止:wait/waitpid
在说明wait函数之前,先来说明一下,下列情形:
1.父进程在子进程之前终止:当父进程终止时,内核会遍历该进程的所有子进程,并把子进程的父进程ID改为1(init进程)。
2.子进程在父进程之前终止:子进程(无论是正常还是异常)终止后,内核会保留子进程的相关信息:这些信息包括子进程ID,子进程的终止状态,子进程使用的CPU时间总量。通常表示父进程需要这些信息,如果父进程还未对这些信息做处理,此时这些子进程就变成僵死进程(zombie)。当父进程调用wait相关函数后,内核才会对僵死进程做处理。子进程才真正在内存中消失。
#include<sys/wait.h>
pid_t wait(int *status); //父进程阻塞,等待任意子进程的结束,子进程结束后,返回子进程ID。子进程状态保存在status中。
pid_t waitpid(pid_t pid, int *status, int options);
对于waitpid函数:
pid > 0 等待进程ID与pid相等的子进程
pid == 0 等待组ID与父进程组ID相等的任意一个子进程
pid == -1 等待任意一个子进程, 与wait等效
pid < -1 等待组ID等于pid绝对值的任意子进程
options 控制waitpid的操作:
0 waitpid阻塞
WNOHANG waitpid不阻塞
WCONTINUED,WUNTRACED 支持作业控制的选项
三:父子进程之间的同步:一般的同步流程如下
TELL_WAIT(); /*set things up for TELL_xxx & WAIT_xxx*/
if(pid == fork() < 0){
perror("fork error"); exit(1);
}else if(pid == 0){ /*child execute first*/
/*child does whatever is necessary*/
TELL_PARENT(getppid()); /*tell parent we are done*/
WAIT_PARENT();/*and wait for parent*/
/*child continues on its way*/
exit(0);
}
/*parent does whatever is necessary*/
WAIT_CHILD();
TELL_CHILD(pid);
/*parent continues on its way*/
exit(0);
下面代码是基于管道的一种同步实现:
- /*父进程读pfd2[0],得到通知;写pfd1[1],在pfd1[1]中写入'c'通知子进程
- 子进程读pfd1[0],得到通知;写pfd2[1],在pfd2[1]中写入'p'通知父进程*/
- static int pfd1[2], pfd2[2];
-
- void TELL_WAIT(void)
- {
- if (pipe(pfd1) < 0 || pipe(pfd2) < 0)
- err_sys("pipe error");
- }
-
- void TELL_PARENT(pid_t pid)
- {
- if (write(pfd2[1], "c", 1) != 1)
- err_sys("write error");
- }
-
- void WAIT_PARENT(void)
- {
- char c;
-
- if (read(pfd1[0], &c, 1) != 1)
- err_sys("read error");
-
- if (c != 'p')
- err_quit("WAIT_PARENT: incorrect data");
- }
-
- void TELL_CHILD(pid_t pid)
- {
- if (write(pfd1[1], "p", 1) != 1)
- err_sys("write error");
- }
-
- void WAIT_CHILD(void)
- {
- char c;
-
- if (read(pfd2[0], &c, 1) != 1)
- err_sys("read error");
-
- if (c != 'c')
- err_quit("WAIT_CHILD: incorrect data");
- }
下面代码是基于信号的一种同步实现:
- static volatile sig_atomic_t sigflag; /*set nonzero by sig handler*/
- static sigset_t newmask,oldmask,zeromask;
-
- static void sig_handler(int signo)
- {
- sigflag = 1;
- }
-
- void TELL_WAIT()
- {
- signal(SIGUSR1,sig_handler);
- signal(SIGUSR2,sig_handler);
-
- sigemptyset(&zeromak);
- sigemptyset(&newmak);
- sigaddmask(&newmak,SIGUSR1);
- sigaddmask(&newmak,SIGUSR2);
-
- /*阻塞SIGUSR1 & SIGUSR2,并将原信号集存储在oldmask中*/
- sigprocmask(SIG_BLOCK,&newmak,&oldmask);
- }
-
- void TELL_CHILD(pid_t pid)
- {
- kill(pid,SIGUSR1);
- }
-
- void TELL_PARENT(pid_t pid)
- {
- kill(pid,SIGUSR2);
- }
-
- void wait()
- {
- while(sigflag == 0)
- sigsuspend(&zeromask); //调用进程挂起
- sigflag = 0;
-
- /*恢复原信号集的值*/
- sigprocmask(SIG_SETMASK,&oldmak,NULL);
- }
-
- void WAIT_PARENT()
- {
- wait();
- }
- void WAIT_CHILD()
- {
- wait();
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。