进程控制——fork系统调用学习笔记

本文详细介绍了Linux中的fork系统调用,包括其工作原理、返回值、可能的错误情况,以及与execve的区别。重点阐述了僵死进程和孤儿进程的概念及其影响,并提供了两个实践示例来巩固fork的使用。
摘要由CSDN通过智能技术生成

一、fork 入门知识

一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;

fork出错可能有两种原因:
1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
2)系统内存不足,这时errno的值被设置为ENOMEM。

每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。

另外,还需提及一点:fork与execve的区别,fork是创建两个一模一样的进程,但是具有父子关系;而execve是将自己的进程替换成别人的进程,execve之后只有一个进程。
通俗的讲:fork是分身,而execve是变身。

僵死进程和孤儿进程含义

僵死进程
fork之后,父进程没有调用wait或者waitpid函数,子进程就退出了,这个时候子进程就成了僵死进程。子进程在退出前操作系统希望父进程得到子进程的退出码,所以父进程一定要wait子进程,这个可以避免僵死进程。
僵死进程的危害:系统内核在子进程退出是就释放其内存,但是系统并没有回收其pid,而系统的pid是有有限的,当系统内的僵死进程超过限制,系统将会崩溃。

孤儿进程
父进程在调用wait或waitpid之前就已经退出了,也就是父进程早于子进程退出,此时init进程就成为了子进程的父进程,init进程为子进程的父进程收集退出状态,从而避免僵死进程。孤儿进程并没有危害,常运行于后台。

通俗的解释僵死进程就是:儿子死了,老子不知道;孤儿进程:老子死了,儿子还在。

下面以两道例题来巩固fork的使用

1、编写一个孤儿进程,这个孤儿进程可以同时创建100个僵死进程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main(void)
{
    pid_t pid=fork();
    if(pid<0)
    {
        printf("%s\n",strerror(errno));
        return -1;
    }
    if(pid>0)
    {
        printf("parent pid = %d and my child pid = %d\n",getppid(),pid);
        exit(0);//父进程退出后,子进程变为孤儿进程
    }else
    {
        int i=0;
        for(;i<100;i++)//不断循环100次
        {
            pid_t pid_tmp=fork();
            if(pid_tmp==0)//返回pid等于0说明是子进程
            {
                exit(0);//子进程退出,由于父进程没有wait,这时子进程就变成了僵死进程
            }
        }
        pause();
    }
    return 0;
}

运行结果
使用命令ps -u vicent(用户名)可以查看linux下的所有进程
这里写图片描述
结果:
这里写图片描述

2、编写两个不同的可执行程序,名称分别为a和b,b为a的子进程。在a程序中调用open函数打开a.txt文件。在b程序不可以调用open或者fopen,只允许调用read函数来实现读取a.txt文件。(a程序中可以使用 fork与execve函数创建子进程)。
a.txt文件内容如下:
hello world

a.c文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int arg,char*args[])
{
    printf("a begin\n");
    int fd = open("a.txt", O_RDONLY);//以只读的方式打开a.txt文件
    if(fd==-1)
    {
        printf("err id %s\n",strerror(errno));
        return -1;
    }
    pid_t pid=fork();
    if(pid<0)
    {
        printf("fork failed %s\n",strerror(errno));
        return -1;
    }
    if(pid>0)//父进程
    {
        close(fd);
    }
    if(pid==0)//子进程
    {
        char s[124];
        memset(s,0,sizeof(s));
        sprintf(s,"%d",fd);//将打开a.txt文件后的文件描述符fd格式化为字符串
        char *args[]={"b",s,NULL};
        if(execve("b",args,NULL)==-1)//将文件描述符fd做为启动参数传递给b程序,如果成功此时的a进程就变成了b进程。如果等于-1说明execve失败。
        {
            printf("exevce failed %s\n",strerror(errno));
        }
    }
    printf("a end\n");
    return 0;
}

b.c文件

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main(int arg, char*args[])
{
    printf("b begin\n");
    if(args[1]==NULL)//b启动的时候,如果没有参数,返回错误
    {
        return -1;
    }
    int fd=atoi(args[1]);//将第二个参数char转换为int,也就是文件描述符fd

    char buf[1024];
    memset(buf,0,sizeof(buf));
    read(fd,buf,sizeof(buf)-1);
    printf("%s\n",buf);
    close(fd);
    printf("b end\n");
    return 0;
}

结果:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值