Unix进程API

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值