一级目录

fork了一个子进程,父子谁先运行?不确定,由调度器说了算,
一般父子进程,谁先退出?一般都是子进程先退出,然后父进程负责进行资源回收

创建多进程

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>  
#include <string>
 
enum
{
        ok=0,
        USAGE_ERR=1
};
void Task()
{
        std::cout<<"我是子进程,我完成了我的任务"<<std::endl;
}
int main(int argc,char*argv[])
{
        if(argc!=2)
        {
			   			 std::cout<<"格式应该为.myprocess num"<<std::endl;
                exit(1);
        }

        int num=std::stoi(argv[1]);//这个输入的是字符串
        for(int i=0;i<num;i++)
        {
                pid_t id=fork();
                if(id==0)
                {
                        //child
                        Task();
                        exit(ok);
                }
                //father
                int status=0;
                pid_t rid=waitpid(id,&status,0);
                if(rid>0)
                {
                        printf("子进程:%d,Exit code:%d\n",rid,WEXITSTATUS(status));
                }
        }
        return 0;
}

首先,这样创建与我们平时创建子进程相比,只多了一个

    int num=std::stoi(argv[1]);//这个输入的是字符串
        for(int i=0;i<num;i++)

解析一下比平时的代码多了什么,首先,在第一次for循环的时候父进程先创建了一个子进程,然后父进程等待子进程结束之后再回收子进程,然后再创建第二个子进程,但如果我想让子进程串行的话,可以改为

#include <iostream>
#include <unistd.h>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
enum
{
        ok=0,
        USAGE_ERR=1
};
void Task()
{
        std::cout<<"我是子进程,我完成了我的任务"<<std::endl;
}
int main(int argc,char*argv[])
{
        if(argc!=2)
        {
                std::cout<<"格式应该为.myprocess num"<<std::endl;
                exit(1);
        }

                std::vector<pid_t> subs;
        int num=std::stoi(argv[1]);//这个输入的是字符串
        for(int i=0;i<num;i++)
        {
        //创建子进程
                pid_t id=fork();
                if(id==0)
                {
                        //child
                        Task();
                        exit(ok);//在这里之后子进程就结束了,变成僵尸状态
                }
         //这里只剩下父进程了
         subs.push_back(id);
        }
               //father
               //等待多进程
               //到这里只有父进程一个执行流,但id值只有一个了,id值是最后一个子进程的pid,我们需要记录别的子进程的pid,所以要用一个vector来记录

                for(auto &pid:subs)
                {
                 int status=0;
                pid_t rid=waitpid(pid,&status,0);
                     if(rid>0)
                {
                        printf("子进程:%d,Exit code:%d\n",rid,WEXITSTATUS(status));
                        
                }
                }

        return 0;
}

写代码要养成封装的习惯哦

#include <iostream>
#include <unistd.h>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
enum
{
    ok = 0,
    USAGE_ERR = 1
};
void Task()
{
    std::cout << "我是子进程,我完成了我的任务" << std::endl;
}
void createchild(std::vector<pid_t>& subs,const int&num)
{
    for (int i = 0;i < num;i++)
    {
        //创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            //child
            Task();
            exit(ok);//在这里之后子进程就结束了,变成僵尸状态
        }
        //这里只剩下父进程了
        subs.push_back(id);
    }
}
void waitchild(const std::vector<pid_t>& subs)
{
    for (auto& pid : subs)
    {
        int status = 0;
        pid_t rid = waitpid(pid, &status, 0);
        if (rid > 0)
        {
            printf("子进程:%d,Exit code:%d\n", rid, WEXITSTATUS(status));
        }
    }
}
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cout << "格式应该为.myprocess num" << std::endl;
        exit(1);
    }

    std::vector<pid_t> subs;
    int num = std::stoi(argv[1]);//这个输入的是字符串
    createchild(subs,num);

    waitchild(subs);
    //father
    //等待多进程
    //到这里只有父进程一个执行流,但id值只有一个了,id值是最后一个子进程的pid,我们需要记录别的子进程的pid,所以要用一个vector来记录

那如果,我要在子进程中调用多个函数呢,不止Task,我还有别的要做的,这里列举多一个Hello;
这里随意列举一下,用的c++11特性的花括号初始化,

#include <iostream>
#include <unistd.h>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
typedef void (*callback_t)();
////void myFunction() {
//    // 一些代码
//}
//
//int main() {
//    // 声明一个函数指针,语法很繁琐
//    void (*funcPtr)() = &myFunction;
//
//    // 调用
//    (*funcPtr)();
//    return 0;
//}
//// 1. 定义类型别名
//typedef void (*create_hanshu)();
//
//void myFunction() {
//    // 一些代码
//}
//
//int main() {
//    // 2. 使用新类型声明变量,看起来像普通变量一样
//    create_hanshu func = &myFunction;
//
//    // 3. 调用
//    func();
//    return 0;
//}
enum
{
    ok = 0,
    USAGE_ERR = 1
};
void Task()
{
    std::cout << "我是子进程,我完成了TASK" << std::endl;
}
void Hello()
{
    std::cout << "我是子进程,我完成了Hello" << std::endl;
}
void createchild(std::vector<pid_t>& subs,const int&num, const std::vector<callback_t>& subs2)
{
    for (int i = 0;i < num;i++)
    {
        //创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            //child
            subs2[i]();
            exit(ok);//在这里之后子进程就结束了,变成僵尸状态
        }
        //这里只剩下父进程了
        subs.push_back(id);
    }
}
void waitchild(const std::vector<pid_t>& subs)
{
    for (auto& pid : subs)
    {
        int status = 0;
        pid_t rid = waitpid(pid, &status, 0);
        if (rid > 0)
        {
            printf("子进程:%d,Exit code:%d\n", rid, WEXITSTATUS(status));
        }
    }
}
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cout << "格式应该为.myprocess num" << std::endl;
        exit(1);
    }

    std::vector<pid_t> subs;
    int num = std::stoi(argv[1]);//这个输入的是字符串
    std::vector<callback_t> subs2{ Hello ,Hello,Task,Task };
    createchild(subs,num,subs2);

    waitchild(subs);
    //father
    //等待多进程
    //到这里只有父进程一个执行流,但id值只有一个了,id值是最后一个子进程的pid,我们需要记录别的子进程的pid,所以要用一个vector来记录

   

    return 0;
}

一般而言,在写函数的传递参数的时候
如果是输入:const &
输出:*
输入输出:&
这样子就会创建一个子进程之后,然后子进程的状态变成僵尸状态,5个子进程都变成僵尸状态后父进程一起回收
在这里插入图片描述

进程程序替换

execl函数

先介绍一个函数
在这里插入图片描述

execl("/usr/bin/ls","-a","-l",NULL);//程序替换函数

它的作用是什么呢?

在这里插入图片描述
如果没有这个函数,正常打印的结果肯定是
在这里插入图片描述
但如果我把他加进去,会发生什么呢在这里插入图片描述

在这里插入图片描述
哎,下面的内容怎么没实现呢,这个函数就是让你直接调到另一个文件去实现别的内容,那后面的代码怎么没有了程序替换之后execl的后序代码不执行,因为被替换了。
exec*系列的函数,成功后没有返回值

原理

你设置的文件目录的代码会覆盖execl会将物理内存中的代码段原本的代码
程序替换没有创建新进程,页表左边没变,pcb没有变化,pid没有变化,没有创建新进程
程序替换的本质:把磁盘中的代码和数据,加载到内存中(IO)
程序运行时,必须加载到内存中,是为了让CPU能够高速、高效地工作。
是谁做这个IO过程的呢?OS!OS必须提供对应的系统调用,完成这个加载的过程
加载器->exec->main函数
当然,替换的时候代码段会发生强制替换

常用例子

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
        printf("我是一个子进程:%d\n",getpid());
        pid_t id=fork();
        if(id==0)
        {
                execl("/usr/bin/ls", "ls""-a","-l",NULL);//程序替换函数
                exit(0);//最好设置为1
        }
        wait(NULL);
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        printf("我的进程正在执行中\n");
        return 0;
}


问题1:上面不是说程序被替换了嘛?为什么下面的printf还是正常执行的?
其实是子进程被覆盖了,父进程不收影响,
问题2:那子进程后面的exit也被覆盖了呀,没有退出码,会崩溃嘛?
几乎所有的 Linux 命令行工具(如 cat、grep、ps 等)在其代码的 main 函数结束前,或者在遇到错误时,都会显式地调用 exit() 函数。ls内部是包含 exit 调用。
就算不是系统内部函数,C 语言标准规定:在 C99 及以后的标准中,如果 main 函数执行到结尾没有返回值,编译器会默认当作 return 0; 处理。
问题3:那这个子进程有必要写exit 0;嘛,不都已经被覆盖了嘛
答案:需要的,如果替换失败,就要执行exit函数了,失败时返回-1;execl返回只要失败,继续往后走,默认程序失败,可以直接设置exit为1

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐