进程

先用几个概念铺垫下:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
本篇文章主要围绕fork()展开。fork函数在新的子进程中运行相同的代码,新的子进程是父进程的一个复制品
下面先上代码主函数:

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

主函数主要就是通过一个swith开关语句,用于分别调用不同的fork函数。
接下来fork0():

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

这段代码运行结果如下:
在这里插入图片描述我们可以看到输出两次Hellow。这是一个简单的fork()程序。所以我们直接在脑海里跑遍代码。其中,fork()创建一个进程,返回值为零时,即为子进程。子进程中只执行输出Hellow from child。
接下来fork1():

在这里插入代码片
oid 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创建进程并用getpid返回了任务的编号,当进行子进程时,子进程得到与父进程用户级虚拟地址空间相同的(但独立)的一份副本,包括代码,数据段等等,所以这时x=1然后执行++语句,然后如图所示。同时,我们可以看到子进程的任务编号为16788,父进程同理可得。
2,下面fork2

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

运行结果如下:
在这里插入图片描述
我们可以看到,出现了多个fork,这时我们接用进程图来理解运行结果。
在这里插入图片描述
然后我们用类似于遍历二叉树的顺序发现正如运行结果一样。

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

在这里插入图片描述
这时的fork更加多了,子进程的个数也变成了很多,画出进程图如下:
在这里插入图片描述
然而惊讶的事情发生了,他没有按照便利二叉树的顺序输出,那我们再改动改动代码。

void fork3()
{
    
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("L2\n");
    if(fork()==0){
     printf("childen,Bye\n");
    }    
    else {
     printf("father,Bye\n");
    }
    
}

运行结果:

在这里插入图片描述

由此可见,只是因为Cpu在某个时间段实现另一个子进程。

4,

运行结果
fork4()
{
    printf("L0\n");
    if (fork() != 0) {
 printf("L1\n");    
 if (fork() != 0) {
     printf("L2\n");
 }
    }
    printf("Bye\n");
}

在这里插入图片描述

在这里插入图片描述
5,

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

在这里插入图片描述
在这里插入图片描述
6,

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

在这里插入图片描述
在这个代码中,我们遇见了一个陌生的代码atexit,然后我们查阅一下其它的博客
发现atexit用来注册在程序正常终止时调用的函数。那么创建子进程时,同样也要调用cleanup,所以输出两个Cleaning up。

7,

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

在这里插入图片描述
这里的代码与上面的不同的地方在于,父进程调用时存在一个死循环所以不会正常退出,单子进程没有收到影响,说明cpu在处理两个进程时,将简单的子进程先跑完,再处理父进程。详见8。
8,

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

在这里插入图片描述
这里,就是在7的基础上将死循环放在子进程,用ps命令发现代表子进程的任务编号还在运行。
在这里插入图片描述
9,

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(),并且运行结果中父进程没有执行完,就转去执行子进程了,所以不难猜测,wait的作用就是让cpu先去执行等待的子进程,所以输出的结果顺序有一点差异。

10,

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

在这里插入图片描述
在这里插入图片描述
我们又遇到了wait,那么还是找专业的,所以都是返回子进程的任务编号。
WIFEXITED:如果子进程通过调用exit或者一个返回(return)正常终止,就返回真。在这个代码中返回的都是真。
WEXITSTATUS:返回一个正常终止的子进程的退出状态。然后,我们还会发现输出是倒着的,这个根据进程图也不难理解。

11,

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的区别

12,接下来一个面试题

#include "csapp.h"
int x=0;
/* $begin fork */
/* $begin wasidefork */
int Fork()
{
    x++;printf("%d\n",x);
    return fork();
}
int main(int argc, char *argv[]) 
{
    
    Fork();
    Fork()&&Fork()||Fork();
    Fork();
    printf("%d*\n",x);
}
/* $end fork */
/* $end wasidefork */

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

题目详解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值