csapp_ECF相关例题笔记

csapp第八章ECF部分相关例题
本篇博客写于csapp_ECF习题\例题笔记之后,故上篇已提到知识点不再重复,这里仅贴出forks.c文件的部分函数


fork1

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

在这里插入图片描述
每个进程都有一个唯一的正数进程ID(PID)。gitpid函数返回调用进程的PID。getppid函数返回它的父进程的PID。


fork3

/*
 * fork3 - Three consective forks
 * Parent and child can continue forking
 */
void fork3()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("L2\n");    
    fork();
    printf("Bye\n");
}

在这里插入图片描述
在这里插入图片描述
这里是一个很典型的创建子进程输出语句的进程图,程序运行结果满足拓扑排序。下面一组程序说明使进程选择性的输出语句是什么结果。


fork4,5

/* 
 * fork4 - Nested forks in parents
 */
void fork4()
{
    printf("L0\n");
    if (fork() != 0) {
	printf("L1\n");    
	if (fork() != 0) {
	    printf("L2\n");
	}
    }
    printf("Bye\n");
}

/*
 * fork5 - Nested forks in children
 */
void fork5()
{
    printf("L0\n");
    if (fork() == 0) {
	printf("L1\n");    
	if (fork() == 0) {
	    printf("L2\n");
	}
    }
    printf("Bye\n");
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里能看到这些进程都是父进程先执行


fork6

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函数
函数声明:int atexit(void (*func)(void));
atexit函数来注册程序正常终止时要被调用的函数。(在一个程序中最多可以用atexit注册32个处理函数,这些处理函数的调用顺序与其注册的顺序相反。)


fork7,8

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

/* 
 * fork8 - Demonstration of nonterminating child.  
 * Child still running even though parent terminated
 * Must kill explicitly
 */
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);
    }
}

在这里插入图片描述
注意当./forks 7时,父进程陷入循环不能回到shell,无法敲入下一个命令;而./forks 8时,由于是子进程陷入循环,能继续敲命令。
这里Ctrl+Z后,会导致内核发送一个SIGTSTP信号到前台进程组中的每个进程,挂起(停止)前台作业。
我们可以理解为父进程是shell main中创建的,它循环的时候被Ctrl+Z挂起,回到了shell,而子进程并非shell main创建,所以子进程循环的时候不需要Ctrl+Z终止也能回到shell。
通过ps查看状态,看到defunct,这说明进程3834是一个孤儿进程。如果一个进程因为某种原因终止时,进程并不是立即把他从系统中清楚·。它将保持在已终止,等待被父进程回收的状态中,这样的进程称为僵死进程·。
在这里插入图片描述
&:在后台运行;与./forks 7之后Ctrl+Z将fork7的父进程挂起之后的状态相似


fork9

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

在这里插入图片描述
wait函数
函数声明:pid_t wait(int *statusp);
wait函数是waitpid函数的简单版本。返回:成功则为子进程的PID,出错则为-1。调用wait(&status)等价于调用waitpid(-1,&status,0)。
能看到先是父进程输出HP:,然后wait函数等待子进程终止,子进程输出完紧接着父进程才会继续输出。


fork 10,11

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

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

在这里插入图片描述
从子进程的退出状态看出forks11实现了倒序回收子进程,而forks10由于进程的并发是无序的


信号
fork13

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

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函数的调用和int_handler函数的定义将得到如下结果:

Killing process 3906
Killing process 3907
Killing process 3908
Killing process 3909
Killing process 3910
Child 3908 terminated abnormally
Child 3910 terminated abnormally
Child 3906 terminated abnormally
Child 3907 terminated abnormally
Child 3909 terminated abnormally

signal函数
sighandler_t signal(int signum, sighandler_t handler);
handler是用户定义的函数的地址,这个函数也叫信号处理程序,进程接收到类型为signum的信号就会调用该程序。
这里打印语句输出:接受信号2。但此时是执行int_handler函数。


fork14,15

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

/*
 * 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();
    }
}

在这里插入图片描述

fork14,
进程p从内核模式切换到用户模式时,会检查进程未被阻塞的待处理信号的集合,集合非空则内核将选择集合中的信号k(最小的),强制p接收k,故而某子进程终止会调用signal函数转而执行child_handler。
sleep函数会将一个进程挂起一段指定的时间。
父进程中sleep在短暂的挂起时间内接收到了子进程的结束信号,便会执行child_handler函数,sleep时间结束,子进程退出,这里仅回收一个子进程。
fork15,
父进程中pause会等到接收一个信号,在这之前child_handler2中由于存在循环,所以结束时已回收所有子进程,count也减为0,故父进程会跳出循环。

这里在信号处理函数里循环调用wait函数比写在父进程里更能节约时间。


fork16,17

/* 
 * fork16 - Demonstration of using /bin/kill program 
 */
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);
    }
} 

/* 
 * Demonstration of using ctrl-c and ctrl-z 
 */
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);
} 

在这里插入图片描述
在这里插入图片描述
getpgrp函数返回当前进程进程组的pid,事实上也就是父进程的pid。


打印多少个*

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

在这里插入图片描述
逻辑运算符的特点,先看A&&B||C:
若A为假,则无需判断B,直接判断C;
若A为真,则判断B:若B也为真,则C无需判断;若B为假,则还要判断C。
在这里插入图片描述
这里的1和0是指Fork的返回值,父进程返回子进程的pid(大于0这里设1),子进程返回0。由图知最后除了父进程还会产生19个子进程,一共20个进程故输出20个*。


#include "csapp.h"
int main(void){
    int i;
    for(i=0;i<2;i++){

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

在这里插入图片描述
在这里插入图片描述
这里的*未在第一个fork后输出,而是两个都留在了缓冲区,导致第二个fork后的子进程也拥有了缓冲区的**,所以四个进程会一共输出八个星号。可以在后面加上换行符\n或者fflush(stdout)及时清空缓冲区的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值