ECF入门(下)

上一次聊了聊fork,接下来我们看一看signal
首先讲一下LINUX中的一部分下面会用到的信号:

2 SIGINT 来自键盘的中断
9 SIGKILL 杀死程序
11 SIGSEGV 无效的内存引用(段故障)
14 SIGALARM 来自alarm函数的定时器信号
17 SIGCHILD 一个子进程停止或终止

接下来来看几个下面会用到的函数:

kill函数

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int sig)

pid>0 kill函数发送sig信号给指定的进程pid
pid=0 kill函数发送sig信号给调用进程所在进程组的每个进程,包括调用进程自己
pid<0 kill函数发送sig信号给进程组|pid|中的每个进程

alarm函数

#include <unistd.h>
unsigned int alarm(unsigned int secs);

alarm函数安排内核在secs秒后发送一个SIGALARM信号给调用进程

signal函数

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler)

每个信号其实是由相关联的默认行为的,但是 signal函数可以规定接收到除了SIGKILL和SIGSTOP以外的信号的操作。
如果handler是SIG_IGN的话,就忽略到signum信号
若果handler是SIG_DEL的话,就恢复signum信号的默认行为
否则程序受到signum是就调用handler定义的信号处理程序进行运行。

然后放一下Linux的信号表:在这里插入图片描述

接下来我们看看程序

fork12

void fork12()
{
    pid_t pid[N];
    int i;
    int child_status;

    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0) {
	    /* Child: Infinite Loop */
	    while(1)
		;
	}
    for (i = 0; i < N; i++) {
	printf("Killing process %d\n", pid[i]);
	kill(pid[i], SIGINT);
    }

    for (i = 0; i < N; i++) {
	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 terminated abnormally\n", wpid);
    }
}

这里运行了五次fork,每一次fork产生的子进程都陷入了死循环。
在这里插入图片描述
然后,父进程给PID为pid[0]-pid[4]的子进程发送了一个来自键盘的中断信号SIGINT,因为没有定义signal接收到SIGINT的行为,所以它的默认行为就是终止。
在这里插入图片描述
在这里插入图片描述
接着父进程继续运行wait函数查看子进程是否终止,他会查看N次,正好对应N个子进程,然后发现子进程都终止了,但都不是调用exit或者return正常终止的,所以在这里查看WIFEXITED返回的是假,那么就会打印后面一句非正常终止。
程序运行结果如下:
在这里插入图片描述

fork13

/*
 * int_handler - SIGINT handler
 */
void int_handler(int sig)
{
    printf("Process %d received signal %d\n", getpid(), sig); /* Unsafe */
    exit(0);
}

/*
 * fork13 - Simple signal handler example
 */
void fork13()
{
    pid_t pid[N];
    int i;
    int child_status;

    signal(SIGINT, int_handler);
    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0) {
	    /* Child: Infinite Loop */
	    while(1)
		;
	}

    for (i = 0; i < N; i++) {
	printf("Killing process %d\n", pid[i]);
	kill(pid[i], SIGINT);
    }

    for (i = 0; i < N; i++) {
	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 terminated abnormally\n", wpid);
    }
}

这个程序和上面一个十分相像,但是它用signal(SIGINT, int_handler)修改了进程受到SIGINT时的默认行为。从原来表中查询到的中断变成了打印一句话,然后正常退出,所以后续的WIFEXITED就为真,打印了上一句话。因为程序长得差不多,操作也几乎差不多,这里就不再放进程图了。
运行结果:
在这里插入图片描述

fork14

/*
 * child_handler - SIGCHLD handler that reaps one terminated child
 */
int ccount = 0;
void child_handler(int sig)
{
    int child_status;
    pid_t pid = wait(&child_status);
    ccount--;
    printf("Received SIGCHLD signal %d for process %d\n", sig, pid); /* Unsafe */
    fflush(stdout); /* Unsafe */
}

/*
 * fork14 - Signal funkiness: Pending signals are not queued
 */
void fork14()
{
    pid_t pid[N];
    int i;
    ccount = N;
    signal(SIGCHLD, child_handler);

    for (i = 0; i < N; i++) {
	if ((pid[i] = fork()) == 0) {
	    sleep(1);
	    exit(0);  /* Child: Exit */
	}
    }
    while (ccount > 0)
	;
}

这里就是fork了五个子进程,然后让他们各自休眠1,然后子进程退出,发送SIGCHILD信号给父进程,父进程调用child_handler函数,随机检查一个进程的状态,然后打印,一些操作后程序陷入死循环。
先看一下结果:
在这里插入图片描述
然后我们来分析一下(只是我的拙见):
首先先fork出的进程先结束休眠退出发信号,但是这些等待父进程处理的信号并不是有序排队的,所以父进程在还未进入死循环之前处理了一部分信号,就如图所示,紧接着父进程进入死循环,就不在处理信号了,子进程就变成僵死进程。

/*
 * child_handler2 - SIGCHLD handler that reaps all terminated children
 */
void child_handler2(int sig)
{
    int child_status;
    pid_t pid;
    while ((pid = wait(&child_status)) > 0) {
	ccount--;
	printf("Received signal %d from process %d\n", sig, pid); /* Unsafe */
	fflush(stdout); /* Unsafe */
    }
}
/*
 * fork15 - Using a handler that reaps multiple children
 */
void fork15()
{
    pid_t pid[N];
    int i;
    ccount = N;

    signal(SIGCHLD, child_handler2);

    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0) {
	    sleep(1);
	    exit(0); /* Child: Exit */

	}
    while (ccount > 0) {
	pause();
    }
}

先看一下运行结果:
在这里插入图片描述
然后我再来发表一下拙见:这里首先最后的死循环里面用到了pause函数,pause函数使得父进程一直能够等待接收外来的信号,所以上一个程序里出现的子进程退出发送的SIGCHILD父进程接收不到的问题就不会再出现了,此外在接收到SIGCHILD后调用的程序里,添加了一条循环,只要有子进程退出,他都要打印出来并且回收。(但这里我感觉其实起作用的还是在于handler2,因为这里会一直等子进程结束,其实pause不会起到真正的作用)

fork16

void fork16()
{
    if (fork() == 0) {
	printf("Child1: pid=%d pgrp=%d\n",
	       getpid(), getpgrp());
	if (fork() == 0)
	    printf("Child2: pid=%d pgrp=%d\n",
		   getpid(), getpgrp());
	while(1);
    }
}

fork17

void fork17()
{
    if (fork() == 0) {
	printf("Child: pid=%d pgrp=%d\n",
	       getpid(), getpgrp());
    }
    else {
	printf("Parent: pid=%d pgrp=%d\n",
	       getpid(), getpgrp());
    }
    while(1);
}

这里我们要了解到一个组的概念,这里简单来说就是凡是一个父进程fork出来的子进程都在和父进程的一个组里。
来看看果然如此的结果:
在这里插入图片描述
本文部分图片引用自CMU的CSAPP

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值