本节内容主要是介绍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();
}