学习日志之异常控制流

本文主要讲Unix系统调用错误处理(用fork函数检查错误)以及系统调用函数控制进程

1.fork函数以及getpid,getppid函数

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回:子进程返回0,父进程返回子进程的PID,如果出错,则为-1.
pid_t getpid(void);
返回:调用者的PID.
pid_t getppid(void);
返回:其父进程的PID.

需要注意的是fork函数只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork 返回子进程的PID。在子进程中,fork返回0。因为子进程的PID总是为非零,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。

example

/* 
 * fork1 - Simple fork example 
 * Parent and child both run same code
 * Child starts with identical private state
 */
void fork1()
{
    int x = 1;
    pid_t pid = fork();

    if (pid == 0) {
	printf("Child has x = %d\n", ++x);
    } 
    else {
	printf("Parent has x = %d\n", --x);
    }
    printf("Bye from process %d with x = %d\n", getpid(), x);
}

运行结果如下
在这里插入图片描述
分析
在这里插入图片描述
注:上图中的pid为程序中的pid。
我们先看到的是父进程的结果,再是子进程的结果。(这只是这台机器运行一次的结果,也可以是先进行子进程,再是父进程)

2.exit函数

#include <stdlib.h>
void exit(int status);
(exit函数以status退出状态来终止进程)

example

void cleanup(void) {
    printf("Cleaning up\n");
}

/*
 * fork6 - Exit system call terminates process
 * call once, return never
 */
void fork6()
{
    atexit(cleanup);
    fork();
    exit(0);
}

运行结果如下
在这里插入图片描述
分析
在这里插入图片描述
关于atexit()函数,可查看atexit函数详解(转载)

3.kill指令及僵死进程

$kill -sig pid
发送信号sig给进程pid

example

/* 
 * fork7 - Demonstration of zombies.
 * Run in background and then perform ps 
 */
void fork7()
{
    if (fork() == 0) {
	/* Child */
	printf("Terminating Child, PID = %d\n", getpid());
	exit(0);
    } else {
	printf("Running Parent, PID = %d\n", getpid());
	while (1)
	    ; /* Infinite loop */
    }
}

运行结果如下
在这里插入图片描述
注:ps指令用于显示后台进程 kill -9中的9为杀死程序信号的序号
父进程(2401)进入死循环,无法回收已经结束的子进程(2402)
之后我们用kill -9 2401 杀死父进程后,僵死的子进程被init进程回收

4.wait函数
等待一个子进程结束

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statusp)
返回:如果成功,则为子进程的PID,如果出错,则为-1。

example1

/*
 * fork9 - synchronizing with and reaping children (wait)
 */
void fork9()
{
    int child_status;

    if (fork() == 0) {
	printf("HC: hello from child\n");
        exit(0);
    } else {
	printf("HP: hello from parent\n");
	wait(&child_status);
	printf("CT: child has terminated\n");
    }
    printf("Bye\n");
}

运行结果如下
在这里插入图片描述
分析
在这里插入图片描述
example2

#define N 5
/* 
 * fork10 - Synchronizing with multiple children (wait)
 * Reaps children in arbitrary order
 * WIFEXITED and WEXITSTATUS to get info about terminated children
*/
void fork10()
{
    pid_t pid[N];
    int i, child_status;

    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0) {
	    exit(100+i); /* Child */
	}
    for (i = 0; i < N; i++) { /* Parent */
	pid_t wpid = wait(&child_status);
	if (WIFEXITED(child_status))
	    printf("Child %d terminated with exit status %d\n",
		   wpid, WEXITSTATUS(child_status));
	else
	    printf("Child %d terminate abnormally\n", wpid);
    }
}

运行结果如下
在这里插入图片描述
分析
注:WIFEXITED(status):如果子进程通过调用exit或者一个返回(return)正常终止,就返回真。
WEXITSTATUS(status):返回一个正常终止的子进程的退出状态。只有在WIFEXITED()返回为真时,才会定义这个状态。
在这里插入图片描述
注意,程序不会按照特定的顺序回收子进程。子进程回收的顺序是这台特定的计算机系统的属性。在另一个系统上,甚至在同一个系统上再执行一次,两个子进程都 能以相反的顺序被回收。这是非确定性行为的一个示例,这种非确定性行为使得对并发进行推理非常困难。两种可能的结果都同样是正确的,作为一个程序员,你绝不可以假设总是会出现某一个结果,无论多么不可能出现另一个结果。唯一正确的假设是每一个可能的结果都同样可能出现。

5.fflush函数与缓冲区
关于fflush函数

#include <stdio.h>
int fflush(FILE *stream)
fflush()会强迫将缓冲区内的数据写回参数stream 指定的文件中. 如果参数stream 为NULL,fflush()会将所有打开的文件数据更新。

fflush(stdin):刷新缓冲区把缓冲里面的东西清空
fflush(stdout):刷新缓冲区把缓冲里面的内容输出到设备上
关于缓冲区
只有当碰到‘\n’或fflush(stdout)或缓冲区满或程序结束时,缓冲区中的内容才会输出。

在讲下面三个例子前,有几个点先在此说明
例子2和3中的Fork()为csapp.h中定义的函数,是fork的变型,原型为

pid_t Fork(void)
{
pid_t pid;
if ((pid = fork()) < 0)
unix_error(“Fork error”);
return pid;
}

example1

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void){
	int i;
	for(i=0;i<2;i++){

		fork();
        printf("*");
	}
	return 0;
}

运行结果如下
在这里插入图片描述
分析:

在这里插入图片描述
黑色星为缓冲区的内容,红色星为输出内容
子进程产生时也将父进程的缓冲区复制了,所以输出时一共有8个星

example2

/* $begin forkprob2 */
#include "csapp.h"

void end(void) 
{
    printf("2"); fflush(stdout);
}

int main() 
{
    if (Fork() == 0) 
	atexit(end);
    if (Fork() == 0){
		printf("0");fflush(stdout);
	}
    else{
		printf("1");fflush(stdout);
	}
    exit(0);
}
/* $end forkprob2 */

运行结果如下
在这里插入图片描述
分析
在这里插入图片描述
注:黑色数字为缓冲区内容,红色数字为输出内容
example3

#include <stdio.h>
#include <stdlib.h>
#include <csapp.h>
int main(int argc,char* argv[])
{
    Fork();
    Fork()&&Fork()||Fork();
    Fork();
    printf("*\n");
    sleep(5);
}

运行结果如下
在这里插入图片描述
分析:Fork()函数依次编号,为1,2,3,4,5。
关于 Fork()&&Fork()||Fork()这段代码,机器运行顺序为

在这里插入图片描述
分析
在这里插入图片描述
注:A和B一样,因为纸张大小有限,我就没画了
实际上真就是父进程(pid>0即Fork>0),假就是子进程(pid=0即Fork=0)从图中我们可以看出B这边有10个星,再加上A总共20个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值