计算机系统基础No.10

一点前提

这里推荐一个画进程图,思维导图的免费网站----迅捷画图,虽然导出成图片需要VIP,但显然,电脑自带的截图功能就能让我们成功白嫖,下面的fork进程图便是这么画出来的

具体实例

就部分fork实例来具体介绍fork

头文件

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> 
#include <signal.h>

主函数

int main(int argc, char *argv[])
{
    int option = 0;
    if (argc > 1)
	option = atoi(argv[1]);
    switch(option) {
    case 0: fork0();
	break;
    case 1: fork1();
	break;
    case 2: fork2();
	break;
    case 3: fork3();
	break;
    case 4: fork4();
	break;
    case 5: fork5();
	break;
    case 6: fork6();
	break;
    case 7: fork7();
	break;
    case 8: fork8();
	break;
    case 9: fork9();
	break;
    case 10: fork10();
	break;
    case 11: fork11();
	break;
    case 12: fork12();
	break;
    case 13: fork13();
	break;
    case 14: fork14();
	break;
    case 15: fork15();
	break;
    case 16: fork16();
	break;
    case 17: fork17();
	break;
    default:
	printf("Unknown option %d\n", option);
	break;
    }
    return 0;
}

fork0.c

源程序

void fork0() 
{
    if (fork() == 0) {
	printf("Hello from child\n");
    }
    else {
	printf("Hello from parent\n");
    }
}

运行结果在这里插入图片描述
进程图
在这里插入图片描述

fork1.c

源程序

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);
}

在这里插入图片描述
进程图
在这里插入图片描述
因为fork()后子进程将复制父进程结构,所以子进程的x不受父进程的影响

fork2.c

源程序

void fork2()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("Bye\n");
}

在这里插入图片描述
进程图
在这里插入图片描述

fork3.c

源程序

void fork3()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("L2\n");    
    fork();
    printf("Bye\n");
}

在这里插入图片描述
进程图
在这里插入图片描述

fork4

源程序

void fork4()
{
    printf("L0\n");
    if (fork() != 0) {
	printf("L1\n");    
	if (fork() != 0) {
	    printf("L2\n");
	}
    }
    printf("Bye\n");
}

在这里插入图片描述
进程图
在这里插入图片描述
子进程fork=0,所以第一个子进程无法输出L1,也没法继续fork,第二个无法输出L2

fork5.c

void fork5()
{
    printf("L0\n");
    if (fork() == 0) {
	printf("L1\n");    
	if (fork() == 0) {
	    printf("L2\n");
	}
    }
    printf("Bye\n");
}

在这里插入图片描述
进程图
在这里插入图片描述
进程结构图与上一题一样,区别在于一个是!=,另一个是==

fork6.c

void cleanup(void) {
    printf("Cleaning up\n");
}
void fork6()
{
    atexit(cleanup);
    fork();
    exit(0);
}

在这里插入图片描述
进程图
在这里插入图片描述
函数名: atexit

头文件:#include<stdlib.h>

功 能: 注册终止函数(即main执行结束后调用的函数)

用 法: int atexit(void (*func)(void));

注意:一个进程可以登记32个函数,这些函数由exit自动调用,这些函数被称为终止处理函数,atexit函数可以登记这些函数。 exit调用终止处理函数的顺序和atexit登记的顺序相反,如果一个函数被多次登记,也会被多次调用。

fork7.c

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 */
    }
}

在这里插入图片描述
进程图
在这里插入图片描述
父进程将一直在运行除非使用kill命令才会运行子进程
getpid函数是显示进程号

fork8.c

void fork8()
{
    if (fork() == 0) {
	/* Child */
	printf("Running Child, PID = %d\n",
	       getpid());
	while (1)
	    ; /* Infinite loop */
    } else {
	printf("Terminating Parent, PID = %d\n",
	       getpid());
	exit(0);
    }
}

在这里插入图片描述
进程图
在这里插入图片描述
子进程死循环,但父进程已经运行完毕退出,所以子进程将一直运行,可以用kill杀死

fork9

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");
}

在这里插入图片描述
进程图
在这里插入图片描述
wait和waitpid出现的原因
SIGCHLD
–当子进程退出的时候,内核会向父进程SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止)
–子进程退出时,内核将子进程置为僵尸状态,这个进程成为僵尸进程,它只保留最小的一些内核数据结构,以便父进程查询子进程的退出状态
–父进程查询子进程的退出状态可以用wait/waitpid函数

fork10.c

#define N 5
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) 如果进程子进程正常结束,返回一个非零值
WEXITSTATUS(status) 如果WIFEXITED非零,返回子进程退出码
WIFSIGNALED(status) 子进程因为捕获信号而终止,返回非零值
WTERMSIG(status) 如果WIFSIGNALED非零,返回信号代码
WIFSTOPPED(status) 如果进程被暂停,返回一个非零值
WSTOPSIG(status) 如果WIFSTOPPED非零,返回信号代码

for循环5次,子进程由exit直接退出,父进程调用wait,可输出结束的子进程号,以及状态码

fork11

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

    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0)
	    exit(100+i); /* Child */
    for (i = N-1; i >= 0; i--) {
	pid_t wpid = waitpid(pid[i], &child_status, 0);
	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);
    }
}

在这里插入图片描述
介绍wait和waitpid区别
在一个进程终止前,wait 使其调用者阻塞
waitpid 函数有一个选择项,可以使调用者不阻塞
waitpid 等待一个指定的子进程,wait 等待所有的子进程,返回任一子进程的终止状态
父进程从大到小,故子进程进程号从大到小输出

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);
    }
}

在这里插入图片描述
介绍kill函数
kill函数/命令产生信号
kill命令产生信号:kill -SIGKILL pid
kill函数:给指定进程发送指定信号(不一定杀死)
int kill(pid_t pid, int sig);
返回值说明:
成功执行时,返回0。失败返回-1,errno被设为以下的某个值 EINVAL:指定的信号码无效(参数 sig 不合法) EPERM;权限不够无法传送信号给指定进程 ESRCH:参数 pid 所指定的进程或进程组不存在
参数:
pid:可能选择有以下四种
1.pid>零时,pid是信号欲送往的进程的标识。
2. pid=零时,信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
3. pid=-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
4. pid<-1时,信号将送往以-pid为组标识的进程。
子进程死循环,父进程中杀死了子进程,子进程非正常结束。
杀死顺序从小到大,但结果输出顺序随机

fork13.c

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
表头文件#include<signal.h>
功 能:设置某一信号的对应动作
函数原型:void (signal(int signum,void( handler)(int)))(int);
或者:typedef void(*sig_t) ( int );
sig_t signal(int signum,sig_t handler);
参数说明:
第一个参数signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。
第二个参数handler描述了与信号关联的动作,它可以取以下三种值:
(1)一个无返回值的函数地址
此函数必须在signal()被调用前申明,handler中为这个函数的名字。当接收到一个类型为sig的信号时,就执行handler 所指定的函数。这个函数应有如下形式的定义:
void func(int sig);
sig是传递给它的唯一参数。执行了signal()调用后,进程只要接收到类型为sig的信号,不管其正在执行程序的哪一部分,就立即执行func()函数。当func()函数执行结束后,控制权返回进程被中断的那一点继续执行。
(2)SIG_IGN
这个符号表示忽略该信号,执行了相应的signal()调用后,进程会忽略类型为sig的信号。
(3)SIG_DFL
这个符号表示恢复系统对信号的默认处理。
函数说明:
signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。
返回值:返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。
下面的情况可以产生Signal:
按下CTRL+C产生SIGINT
硬件中断,如除0,非法内存访问(SIGSEV)等等
Kill函数可以对进程发送Signal
Kill命令。实际上是对Kill函数的一个包装
软件中断。如当Alarm Clock超时(SIGURG),当Reader中止之后又向管道写数据(SIGPIPE),等等

fork14.c

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 */
}

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)
	;
}

在这里插入图片描述
参考他人博客
在Linux系统下,子进程状态改变后产生此信号,如子进程停止(STOP)、子进程终止(terminate)等。

在Linux系统下,如果进程明确地将该信号的配置设置为SIG_IGN,则调用进程的子进程将不产生僵死进程。注意,这与其默认动作(SIG_DFL)“忽略”不同。

在Linux系统下,如果使用sigaction将信号SIGCHLD的sa_flags中的SA_NOCLDSTOP选项打开,当子进程停止(STOP作业控制)时,不产生此信号(即SIGCHLD)。不过,当子进程终止时,仍旧产生此信号(即SIGCHLD)。

在Linux系统下,如果使用sigaction将信号SIGCHLD的sa_flags中的SA_NOCLDWAIT选项打开,则当调用进程的子进程终止时,不创建僵死进程(Zombie)。

POSIX.1说明,在SIGCHLD未决(pending)期间,如若wait或waitpid返回了子进程的状态,那么SIGCHLD信号不应递送(delivery)给该父进程,除非另一个子进程的状态也可用。FreeBSD 8.0、Mac OS X10.6.8和Solaris10都实现了这种语义。但是Linux3.16.0没有实现这种语义,在调用进程阻塞(Block)了SIGCHLD后,并且在调用进程waitpid子进程之后,SIGCHLD保持为未决;当解除了对此信号的阻塞后,它被递送至调用者进程。如果在调用进程中捕获了SIGCHLD,在捕获函数中调用wait,Linux系统将返回-1,并将errno设置为ECHILD。这是因为已经取到了子进程的终止状态。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值