• 【Linux】进程控制


    🌈前言

    本篇文章进行操作系统中进程控制的学习!!!


    🌸1、进程创建

    🍡1.1、概念

    fork函数初识:

    • 在Linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程

    • 新进程为子进程,而原进程为父进程

    #include 
    pid_t fork(void);
    返回值:子进程中返回0,父进程返回子进程的pid,出错(创建子进程失败)返回-1
    
    • 1
    • 2
    • 3

    进程调用fork,当控制转移到内核中的fork代码后,内核做了以下工作:

    • 分配新的内存块和内核数据结构给子进程(已知task_struct、mm_struct和页表)

    • 将父进程部分数据结构内容拷贝至子进程

    • 添加子进程到系统进程列表(运行队列)当中

    • fork返回时,开始调度器调度

    进程 = 内核的进程数据结构 + 进程的代码和数据

    • 创建子进程的内核数据结构(struct task_struct + struct mm_struct + 页表)+ 代码继承父进程,数据通过写时拷贝,来实现父子进程之间的共享和独立!!!
    [lyh_sky@localhost lesson14]$ ls
    process2  process2.c
    [lyh_sky@localhost lesson14]$ cat process2.c 
    #include 
    #include 
    
    int main()
    {
        printf("我是一个进程: %d\n", getpid());
        fork();
        printf("我依旧是一个进程: %d\n", getpid());
        return 0;
    }
    [lyh_sky@localhost lesson14]$ ./process2 
    我是一个进程: 8968         // 父进程
    我依旧是一个进程: 8968		 // 父进程
    我依旧是一个进程: 8969	     // 子进程
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    🍢1.2、fork()之后执行顺序

    fork之后子进程是从哪里开始执行的呢?

    • fork之前父进程独立执行,fork之后就有两个进程了,父子两个执行流分别执行

    • fork之后,父子进程共享所有的代码,而不是拷贝fork之后的代码!!!

    • 但是子进程执行的后续代码不等于共享的所有代码,只不过子进程只能从这里开始执行

    • CPU中有一个EIP寄存器:它是一个程序计数器,作用是保存当前正在执行代码的下一条代码

    • EIP寄存器会拷贝给子进程,子进程便从EIP所指向的代码处开始执行!!!

    这里子进程没有执行Before这条打印,因为EIP寄存器的原因!!!

    [lyh_sky@localhost lesson14]$ ls
    process2  process2.c
    [lyh_sky@localhost lesson14]$ cat process2.c 
    #include 
    #include 
    
    int main()
    {
        printf("Before PID: %d\n", getpid());
        fork();
        printf("After PID: %d\n", getpid());
        return 0;
    }
    [lyh_sky@localhost lesson14]$ ./process2 
    Before PID: 9751
    After PID: 9751
    After PID: 9752
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述


    🍧1.3、fork()返回值

    • 子进程返回0,
    • 父进程返回的是子进程的pid

    通过if else分流的方式来分别执行父子进程代码,各自完成自己的工作!!!

    #include 
    #include 
    using namespace std;
    
    int main()
    {
        pid_t id = fork();
    
        if (id == 0)
        {
            while (1)
            {
                cout << "我是子进程, 我的pid: " << getpid()
                  << ", 我的父进程是: " << getppid() << endl;
                sleep(3);
            }
        }
        else
        {
            while (1)
            {
                cout << "我是父进程, 我的pid: " << getpid()
                  << ", 我的父进程是: " << getppid() << endl;           
                sleep(3);
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述


    🍨1.4、写时拷贝

    当fork一个子进程后:

    • 通常,父子代码共享,父子再不写入时,数据也是共享的

    • 当任意一方试图写入,便以写时拷贝的方式各自一份副本(独立性)

    写时拷贝本身是由OS中内存管理模块管理的!!!

    在这里插入图片描述

    为什么要有写时拷贝???

    • 写时拷贝:如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用都是透明的(transparently)

    • 提高效率和节省内存

    创建子进程的时候,直接把数据分开不行吗???

    • 【浪费内存资源】父进程的数据,子进程不一定使用,即便使用,也不一定全部进行写入

    • 【效率低】如果fork的时候,就无脑的拷贝数据给子进程,会增加fork的成本(内存和时间)

    采用写时拷贝的好处:

    • 只会拷贝父子进程中其中一个数据修改的,变相的,就是拷贝数据的最小成本

    • 写时拷贝采用延时拷贝策略,只有当你真正使用的时候,才进行拷贝

    • 你想要,但是你不立马使用该空间,那先不给你,就意味着这段空间可以给别人使用,变相的提高内存的使用效率!!!


    🍩1.5、fork创建失败问题

    • 系统中有太多的进程
    • 实际用户的进程数超过了限制

    验证:系统有太多进程导致内存资源不足创建失败!!!

    [lyh_sky@localhost lesson14]$ ls
    test  test.c
    [lyh_sky@localhost lesson14]$ cat test.c 
    #include 
    #include 
    #include 
    #include 
    
    int count = 0;
    
    int main()
    {
        while (1)
        {
            pid_t id = fork();
            if (id < 0)
            {
                printf("创建子进程失败!!!\n");
                break;
            }
            else if (id == 0)
            {
                printf("我是一个子进程: %d\n", getpid());
                // 让这个进程存在30秒后结束它
                sleep(30);
                // 结束该进程,错误码返回0
                exit(0);
            }
            ++count;
        }
        printf("一共创建了%d个子进程\n", count);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述


    🍁2、进程终止

    🍲2.1、概念

    关于进程终止的认识:

    1. C/C++中main函数的return 0,是给谁return的呢?
    • 很多人说,是给操作系统返回的0,答案是不准确的

    • 进程会维护一个退出码,表征进程退出的信息,让父进程进行读取

    • bash进程运行一个子进程时,子进程执行结束,会返回一个退出码给bash进程

    echo $?:打印bash进程最近执行完毕的子进程的退出码,第二次会变回0

    [lyh_sky@localhost lesson14]$ ls
    test  test.c
    [lyh_sky@localhost lesson14]$ cat test.c 
    #include 
    
    int main()
    {
        return 20;
    }
    // 在bash进程中运行一个子进程
    [lyh_sky@localhost lesson14]$ ./test 
    [lyh_sky@localhost lesson14]$ echo $?
    20
    [lyh_sky@localhost lesson14]$ echo $?
    0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 为什么return 0,可以return其他值吗?

    常见的进程退出方式:

    1. 代码已经全部执行,结果是正确的

    2. 代码已经全部执行,结果是错误的

    3. 代码没有全部执行,程序出现异常

    • 进程代码跑完,结果是否正确,返回0代表是成功的,非0代表是失败的

    • 我们需要找到失败的原因,所以需要不同的错误码来标识不同的失败原因

    • 一般来说失败时,非0值可以自定义,错误退出码 可以对应不同的错误原因,方便定位问题

    C语言中的错误码

    [lyh_sky@localhost lesson14]$ ls
    errno  errno_code.c
    [lyh_sky@localhost lesson14]$ cat errno_code.c 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
    	// 错误码没有0,只有1~124个错误码
        for (int i = 1; i <= 124; ++i)
        {
            printf("Erron code %d: %s\n", i, strerror(i));
        }
        return 0;
    }
    [lyh_sky@localhost lesson14]$ ./errno
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述


    🍱2.2、常见进程终止方法

    关于进程终止的常见做法:

    1. main函数return,非main返回不行,非main函数return代表该函数已经执行完毕

    2. void exit(int status),调用exit

    3. void _exit(int status),调用_exit

    • 参数:status 定义了进程的终止状态,父进程通过wait来获取该值

    • 说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255

    exit 和 _exit的区别是什么??

    • exit终止进程,刷新缓冲区!!!

    • _exit直接终止进程,不会有任何的刷新操作!!!

    exit函数执行过程:

    1. 执行用户通过 atexit或on_exit定义的清理函数
    2. 关闭所有打开的流,所有的缓存数据均被写入
    3. 调用_exit

    在这里插入图片描述

    exit

    [lyh_sky@localhost lesson14]$ cat test.c 
    #include 
    #include 
    #include 
    
    int main()
    {
        printf("这是一个测试代码!!!");
        exit(0);
        return 0;
    }
    [lyh_sky@localhost lesson14]$ ./test 
    这是一个测试代码!!![lyh_sky@localhost lesson14]$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    _exit

    [lyh_sky@localhost lesson14]$ cat test.c 
    #include 
    #include 
    #include 
    
    int main()
    {
        printf("这是一个测试代码!!!");
        _exit(0);
        return 0;
    }
    [lyh_sky@localhost lesson14]$ ./test 
    [lyh_sky@localhost lesson14]$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    🍰2.3、终止后,内核做了什么?

    • 我们都知道进程由内核数据结构、进程代码和数据组成

    • OS可能不会释放该进程的内核数据结构,比如:task_struct 和 mm_struct

    • OS会将它们放到内核数据结构缓冲池,“slab分配器

    • slab是内存管理模块的数据结构,变相的可以提高内存的利用率!!!


    🍂3、进程等待

    🍰3.1、为什么需要进程等待

    • 前面的篇章说过,子进程退出,父进程如果不管它,子进程就会从S/R状态变为Z状态(僵尸),进而造成内存泄漏的问题!

    • 进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程,就算是OS也无能为力

    • 最后就是,父进程交给子进程的任务完成的如何,我们需要知道,如:子进程运行完成,结果如何,是否正常退出

    • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息


    🍱3.2、进程等待方法

    1. 使用“wait”函数等待子进程退出
    #include
    #include
    pid_t wait(int*status);
    
    • 1
    • 2
    • 3
    • pid_t:成功返回被等待进程pid,等待失败返回-1

    • status:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

    • wait函数可以解决回收子进程的Z(僵尸)状态,让子进程正常进入X(死亡)状态

    举个例子:

    [lyh_sky@localhost lesson14]$ ls
    process2  process2.c
    [lyh_sky@localhost lesson14]$ cat process2.c 
    #include 
    #include 
    #include 
    #include 
    
    int count = 5;
    
    int main()
    {
        pid_t id = fork();
        if (id == 0)
        {
            while (1)
            {
                printf("我是一个子进程, %d秒后退出\n", count);
                sleep(1);
    
                --count;
                if (count == 0)
                {
                    printf("子进程已退出,等待父进程回收!!!\n");
                    break;
                }
            }     
        }
        else
        {
            printf("我是一个父进程,等待子进程退出!!!\n");
            pid_t id = wait(NULL);
            printf("子进程已退出: %d\n", id);
        }
        return 0;
    }
    [lyh_sky@localhost lesson14]$ ./process2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述


    1. 使用“waitpid”函数等待子进程退出
    #include
    #include
    pid_t waitpid(pid_t pid, int *status, int options);
    
    • 1
    • 2
    • 3
    1. 返回值(pid_t):
    • 当正常返回的时候,waitpid返回收集到的子进程的进程PID

    • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0

    • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在

    1. pid参数:
    • pid = -1,等待任一个子进程
    • pid > 0,等待与pid值相等的进程返回给父进程
    1. status参数:
    • WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    • WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
    1. options参数:
    • 如果该参数为0,则默认是阻塞等待

    • WNOHANG(非阻塞等待): 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID


    🍲3.3、获取子进程status

    • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充

    • 通过调用wait/waitpid函数可以从该函数内拿出特定的数据

    • status可以从子进程task_struct中拿出子进程的退出码和退出信号等等…

    • 如果传递NULL,表示不关心子进程的退出状态信息

    • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究低16比特位):

    int是四个字节,那么有32个比特位,这里面每八个比特位标识着一个状态(除了高七位)

    在这里插入图片描述

    子进程代码执行完成,以退出码的方式返回给父进程

    [lyh_sky@localhost lesson14]$ cat process.c 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int cnt = 5;
    
    int main()
    {
        pid_t id = fork();
    
        if (id == 0)
        {
            while (1)
            {
                printf("我是一个子进程: %d\n", getpid());
                sleep(1);
    
    			// 循环执行五次后跳出
                cnt--;
                if (!cnt)
                {
                    break;
                }
            }
            printf("子进程已经执行完毕,即将退出!!!");
            exit(12);
        }
        else
        {
            int status;
            printf("我是一个父进程: %d, 等待子进程退出!!!\n", getpid());
            sleep(8);
            
    		// 阻塞等待
            pid_t ret = waitpid(id, &status, 0);
            // 这里使用右移和按位与的方式获取退出码和终止信号
            printf ("子进程已经退出,pid: %d, 子进程退出码: %d, 进程退出信号: %d\n", ret, (status >> 8) & 0xFF, (status & 0x7F));
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    在这里插入图片描述

    子进程以异常的方式退出,父进程获取异常终止的信号

    [lyh_sky@localhost lesson14]$ cat process.c 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int cnt = 5;
    
    int main()
    {
        pid_t id = fork();
    
        if (id == 0)
        {
            while (1)
            {
                printf("我是一个子进程: %d\n", getpid());
                sleep(1);
    
                cnt--;
                if (!cnt)
                {
                    break;
                }
            }
    
    		// 空指针访问,段错误
            int* p = NULL;
            *p = 100;
    
            printf("子进程已经执行完毕,即将退出!!!");
            exit(12);
        }
        else
        {
            int status;
            printf("我是一个父进程: %d, 等待子进程退出!!!\n", getpid());
            sleep(8);
            
            // 阻塞等待
            pid_t ret = waitpid(id, &status, 0);
            printf ("子进程已经退出,pid: %d, 子进程退出码: %d, 进程退出信号: %d\n", ret, (status >> 8) & 0xFF, (status & 0x7F));
        }
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    在这里插入图片描述

    退出码和终止信号先看谁呢?

    • 一旦进程在执行时出现异常,进程会直接退出,我们只需要关注终止信号即可

    • 退出码是进程执行完毕后,才会返回给父进程


    🍳3.4、options参数

    options有两个参数:分别是0和WNOHANG

    1. 0是:父进程阻塞等待子进程退出,子进程退出后继续完成后面的工作!

    2. WNOHANG是:父进程非阻塞等待

    如何理解父进程阻塞等待呢?

    • 父进程的状态由R状态变成S状态,从运行队列移除到等待队列中,等待子进程退出

    • 子进程退出,本质就是条件就绪,OS将子进程回收,父进程会从S变成R状态,并且获取到子进程的退出状态,然后继续向下执行代码!!!

    • 父进程阻塞在用户界面上就是卡住了不会动,比如scanf 和 cin

    如何理解非阻塞等待呢?

    • 父进程的状态不会变成S状态,调用waitpid时,OS会检测子进程是否退出,如果没有退出,waitpid会直接返回,父进程不会因为条件不满足,就绪阻塞住

    • 这样父进程就能在空闲的时间去执行其他代码,然后再检测一次,然后再执行一段代码…

    自动构建项目

    [lyh_sky@localhost lesson15]$ ls
    makefile  process  process.cpp
    [lyh_sky@localhost lesson15]$ cat makefile 
    process:process.cpp
    	g++ process.cpp -o process -std=c++11
    .PHONY:clean
    clean:
    	rm -rf process
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    #include 
    #include 
    #include 
    #include 
    #include 
    
    typedef void (*handler_t)();
    // 方法集合
    std::vector<handler_t> handlers;
    
    void fun1()
    {
        std::cout << "Hello, I am change one!!!" << std::endl;
    }
    
    void fun2()
    {
        std::cout << "Hello, I am change two!!!" << std::endl;
    }
    
    void load()
    {
    	// 尾插
        handlers.push_back(fun1);
        handlers.push_back(fun2);
    }
    
    int main()
    {
        pid_t id = fork();
    
        if (id == 0)
        {
            while (1)
            {
                std::printf("我是子进程,我的pid是: %d\n", getpid());
                sleep(3);
            }
        }
        else if (id > 0)
        {
            int status;
            while (1)
            {
                pid_t ret = waitpid(id, &status, WNOHANG);
    
                // ret大于0,表示子进程已经退出
                if (ret > 0)
                {
                    std::printf("等待成功,退出信号:%d, 退出码:%d\n", status & 0x7F, (status >> 8) & 0xFF);
                    break;
                }
                // ret等于0,表示子进程没有退出
                else if (ret == 0)
                {
                    std::printf("等待子进程退出失败,那么那我干其他事情去了!!!\n");
                
                    // 如果容器中没有数据,则调用load函数插入
                    if (handlers.empty() != 0)
                        load();
                    for (auto& f : handlers)
                    {
                        f();
                    }
                    std::cout << std::endl;
                    sleep(3);
                }
                else
                {
                    // 出错,暂时不处理
                }
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    在这里插入图片描述


    🍃4、进程程序替换

    🍴4.1、概念和原理

    父进程fork后,子进程执行的是父进程的代码片段,如果让子进程执行磁盘中全新的代码呢?

    1. 父子进程之间代码共享,互不干扰(独立性,数据发生改变是进行“写时拷贝”)

    2. 我们一般在服务器设计(Linux编程)的时候,往往需要子进程干两件种类的事情:

    • 让子进程执行父进程的代码片段(服务器代码)

    • 让子进程执行磁盘中一个全新的可执行程序(通过我们的进程,执行其他人写的进程代码等等)

    • 比如:C/C++调用C/C++、Python、Shell、PHP、Java程序等等…


    进程程序替换的原理:

    • 当父进程创建一个子进程时,它们之间的代码和数据是共享的,页表映射关系也一样

    • 如果将磁盘中的可执行程序,加载到子进程当中

    • 子进程会程序建立映射关系(写时拷贝),谁执行的程序替换,就重新建立谁的映射(子进程)

    • 重新建立映射关系主要是让父子进程彻底的分离,让子进程执行一个全新的程序(独立性)

    注意:

    • 当磁盘中的程序加载到子进程,是没有创建新进程的,因为是程序替换,替换子进程内部的程序
    • 程序替换函数是系统调用,程序代码的替换是通过OS来完成的

    在这里插入图片描述


    🍵4.2、进程程序替换函数

    如何进行程序替换呢?

    原理

    • 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支)

    • 子进程往往要调用一种exec函数以执行另外一个程序

    • 该进程的用户空间代码和数据全部会被新程序替换,从新程序的启动例程开始执行

    • 调用exec并不创建新进程,所以调用exec前后该进程的id并未改变


    以exec开头的函数,统称exec函数,一共有六种,分别是:

    #include `
    int execl(const char *path, const char *arg, ...);
    int execlp(const char *file, const char *arg, ...);
    int execle(const char *path, const char *arg, ...,char *const envp[]);
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    // 全部exec函数都要通过调用它来实现替换功能,它是一个系统函数
    int execve(const char *path, char *const argv[], char *const envp[]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    函数解析:

    • 函数如果调用成功,则加载新的程序从启动代码开始执行,不再返回

    • 如果调用出错则返回-1

    • 所以说,exec函数只有出错的返回值,而没有成功的返回值

    • 参数中的三个点,是可变参数列表,C++里面说过,可以传任意不同的类型数据

    命名理解:

    这些函数原型看起来很容易混,但只要掌握了规律就很好记

    • l(list) : 表示参数采用列表

    • v(vector) : 参数用数组

    • p(path) : 有p自动搜索环境变量PATH

    • e(env) : 表示自己维护环境变量

    在这里插入图片描述


    🍶4.3、替换函数的举例

    🍷4.3.1、execl(详解)

    execl函数:int execl(const char *path, const char *arg, …);

    我们想要执行一个全新的程序,需要做几件事情

    1. 先找到这个程序在哪里(路径)!!!

    2. 程序可能携带的选项进行执行(也可以不带)-- 带选择一般是指令

    如果执行ls -a -l,path填"/usr/bin/ls",arg填"ls",可变参数列表填"-a", “-b”, “NULL”,最后必须为空

    首先来看单进程的程序替换:

    // 调用/usr/bin/ 目录下的指令
    [lyh_sky@localhost lesson16]$ cat myexec.c 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        printf("我是一个进程,我的pid: %d\n", getpid());
        int ret = execl("/usr/bin/ls", "ls","-a", "-l", NULL);
        // 一旦替换成功,后面的printf就不会被打印,替换失败execl会返回-1
        printf("执行完毕,我的pid: %d, ret: %d\n", getpid(), ret);
        return 0;
    }
    
    [lyh_sky@localhost lesson16]$ ./myexec 
    我是一个进程,我的pid: 7388
    总用量 10
    drwxrwxr-x.  2 lyh_sky lyh_sky   82 1118 15:44 .
    drwxrwxr-x. 18 lyh_sky lyh_sky  253 1117 13:42 ..
    -rwxrwxr-x.  1 lyh_sky lyh_sky 8464 1118 15:44 myexec
    -rw-rw-r--.  1 lyh_sky lyh_sky  325 1118 15:44 myexec.c
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    调用C++可执行程序

    [lyh_sky@localhost lesson16]$ cat mycmd.cpp 
    #include 
    using namespace std;
    
    int main()
    {
        cout << "Hello world!!!" << endl; 
        cout << "Hello world!!!" << endl; 
        cout << "Hello world!!!" << endl; 
        cout << "Hello world!!!" << endl; 
        cout << "Hello world!!!" << endl; 
        return 0;
    }
    
    [lyh_sky@localhost lesson16]$ cat myexec.c 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        printf("我是一个进程,我的pid: %d\n", getpid());
        // 可以使用相对路径或绝对路径,这里使用相对路径
        int ret = execl("./mycmd", "mycmd", NULL);
        printf("执行完毕,我的pid: %d, ret: %d\n", getpid(), ret);
        return 0;
    }
    
    [lyh_sky@localhost lesson16]$ ./myexec 
    我是一个进程,我的pid: 7630
    Hello world!!!
    Hello world!!!
    Hello world!!!
    Hello world!!!
    Hello world!!!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    引入进程创建场景

    • 子进程进行程序替换,不会影响到父进程(上面说过:独立性)

    • 当程序替换的时候,数据层面发生了写时拷贝,代码和数据都发生了写时拷贝,完成父子分离

    [lyh_sky@localhost lesson16]$ cat myexec.c 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        printf("我是一个进程,我的pid: %d\n", getpid());
        pid_t id = fork();
    
        if (id == 0)
        {
            printf("我是子进程,我的pid: %d\n", getpid());
            int ret = execl("/usr/bin/pwd", "pwd", NULL);
    
            printf("执行完毕,我是子进程,我的pid: %d\n", getpid());
            // 若替换失败,终止进程,并且进程设置退出码为10
            exit(10);
        }
        else if (id > 0)
        {
            printf("我是父进程,我的pid: %d, 我正在等待子进程退出\n", getpid());
            int status = 0;
            // 阻塞等待
            pid_t res = waitpid(id, &status, 0);
            printf("等待子进程成功, 退出信号: %d, 退出码: %d\n", status&0x7F, (status>>8)& 0xFF);
        }
        return 0;
     }
    
    [lyh_sky@localhost lesson16]$ ./myexec 
    我是一个进程,我的pid: 8888
    我是父进程,我的pid: 8888, 我正在等待子进程退出
    我是子进程,我的pid: 8889
    /home/lyh_sky/Linux_Study/lesson16
    等待子进程成功, 退出信号: 0, 退出码: 0
    
    [lyh_sky@localhost lesson16]$ pwd
    /home/lyh_sky/Linux_Study/lesson16
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    🍸4.3.2、execlp

    int execlp(const char *file, const char *arg, …);

    • l(list) : 表示参数采用列表

    • p(path) : 有p自动搜索环境变量PATH

    通过lp可以知道:该函数不用指定路径即可调用指令,比如调用ls,file填ls即可

    [lyh_sky@localhost lesson16]$ cat myexec.c 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        printf("我是一个进程,我的pid: %d\n", getpid());
        pid_t id = fork();
    
        if (id == 0)
        {
            printf("我是子进程,我的pid: %d\n", getpid());
    
    		// execlp不需要带路径,因为file会自动从PATH环境变量中的路径找
            int ret = execlp("ls", "ls", "-l", NULL);
    
            printf("执行完毕,我是子进程,我的pid: %d\n", getpid());
            exit(10);
        }
        else if (id > 0)
        {
            printf("我是父进程,我的pid: %d, 我正在等待子进程退出\n", getpid());
            int status = 0;
            // 阻塞等待
            pid_t res = waitpid(id, &status, 0);
            sleep(3);
            printf("等待子进程成功, 退出信号: %d, 退出码: %d\n", status&0x7F, (status>>8)& 0xFF);
        }
        return 0;
    }
    
    [lyh_sky@localhost lesson16]$ ./myexec 
    我是一个进程,我的pid: 9370
    我是父进程,我的pid: 9370, 我正在等待子进程退出
    我是子进程,我的pid: 9371
    总用量 36
    -rw-rw-r--. 1 lyh_sky lyh_sky  131 1118 15:35 makefile
    -rwxrwxr-x. 1 lyh_sky lyh_sky 8968 1118 15:25 mycmd
    -rw-rw-r--. 1 lyh_sky lyh_sky  296 1118 15:16 mycmd.cpp
    -rwxrwxr-x. 1 lyh_sky lyh_sky 8672 1118 16:36 myexec
    -rw-rw-r--. 1 lyh_sky lyh_sky 1063 1118 16:36 myexec.c
    等待子进程成功, 退出信号: 0, 退出码: 0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    🍸4.3.3、execv

    int execv(const char *path, char *const argv[]);

    • v(vector) : 参数用数组

    • 解释:argv是一个指针数组,数组里面存储的是字符指针常量,指针常量意思是指针本身不可修改

    • argv里面存储的是:可执行程序/指令,选项的字符串,最后必须为NULL

    [lyh_sky@localhost lesson16]$ cat myexec.c 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        printf("我是一个进程,我的pid: %d\n", getpid());
        pid_t id = fork();
    
        if (id == 0)
        {
            printf("我是子进程,我的pid: %d\n", getpid());
    
    		// 该数组必须与参数类型一致
            char* const _argv[] = {(char*)"ls",(char*)"-l", NULL};
            int ret = execv("/usr/bin/ls", _argv);
    
            printf("执行完毕,我是子进程,我的pid: %d\n", getpid());
            exit(10);
        }
        else if (id > 0)
        {
            printf("我是父进程,我的pid: %d, 我正在等待子进程退出\n", getpid());
            int status = 0;
            // 阻塞等待
            pid_t res = waitpid(id, &status, 0);
            sleep(3);
            printf("等待子进程成功, 退出信号: %d, 退出码: %d\n", status&0x7F, (status>>8)& 0xFF);
        }
        return 0;
    }
    
    [lyh_sky@localhost lesson16]$ ./myexec 
    我是一个进程,我的pid: 9956
    我是父进程,我的pid: 9956, 我正在等待子进程退出
    我是子进程,我的pid: 9957
    总用量 36
    -rw-rw-r--. 1 lyh_sky lyh_sky  131 1118 15:35 makefile
    -rwxrwxr-x. 1 lyh_sky lyh_sky 8968 1118 15:25 mycmd
    -rw-rw-r--. 1 lyh_sky lyh_sky  296 1118 15:16 mycmd.cpp
    -rwxrwxr-x. 1 lyh_sky lyh_sky 8664 1118 16:51 myexec
    -rw-rw-r--. 1 lyh_sky lyh_sky 1175 1118 16:51 myexec.c
    等待子进程成功, 退出信号: 0, 退出码: 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    🍸4.3.4、execvp

    int execvp(const char *file, char *const argv[]);

    • v(vector) : 参数用数组

    • p(path):有p自动搜索环境变量PATH

    这里直接举例子了

    [lyh_sky@localhost lesson16]$ cat myexec.c 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        printf("我是一个进程,我的pid: %d\n", getpid());
        pid_t id = fork();
    
        if (id == 0)
        {
            printf("我是子进程,我的pid: %d\n", getpid());
    
    		// 该数组必须与参数类型一致
            char* const _argv[] = {(char*)"ls",(char*)"-l", NULL};
            int ret = execv("ls", _argv);
    
            printf("执行完毕,我是子进程,我的pid: %d\n", getpid());
            exit(10);
        }
        else if (id > 0)
        {
            printf("我是父进程,我的pid: %d, 我正在等待子进程退出\n", getpid());
            int status = 0;
            // 阻塞等待
            pid_t res = waitpid(id, &status, 0);
            sleep(3);
            printf("等待子进程成功, 退出信号: %d, 退出码: %d\n", status&0x7F, (status>>8)& 0xFF);
        }
        return 0;
    }
    
    [lyh_sky@localhost lesson16]$ ./myexec 
    我是一个进程,我的pid: 9956
    我是父进程,我的pid: 9956, 我正在等待子进程退出
    我是子进程,我的pid: 9957
    总用量 36
    -rw-rw-r--. 1 lyh_sky lyh_sky  131 1118 15:35 makefile
    -rwxrwxr-x. 1 lyh_sky lyh_sky 8968 1118 15:25 mycmd
    -rw-rw-r--. 1 lyh_sky lyh_sky  296 1118 15:16 mycmd.cpp
    -rwxrwxr-x. 1 lyh_sky lyh_sky 8664 1118 16:51 myexec
    -rw-rw-r--. 1 lyh_sky lyh_sky 1175 1118 16:51 myexec.c
    等待子进程成功, 退出信号: 0, 退出码: 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    🍹4.3.5、execle(详解)

    int execle(const char *path, const char *arg, …,char *const envp[]);

    • l(list) : 表示参数采用列表

    • e(env) : 表示自己维护环境变量

    注意:传递新的环境变量过去,会覆盖原来的环境变量

    [lyh_sky@localhost lesson16]$ cat myexec.c 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
        //环境变量的指针声明
        extern char**environ;
    
        printf("我是一个进程,我的pid: %d\n", getpid());
        pid_t id = fork();
    
        if (id == 0)
        {
            printf("我是子进程,我的pid: %d\n", getpid());
    
            char* const _envp[] = {(char*)"MYPATH=You Can See Me!!!", NULL};
            int ret = execle("./mycmd", "mycmd", "NULL", environ);
    
            printf("执行完毕,我是子进程,我的pid: %d\n", getpid());
            exit(10);
        }
        else if (id > 0)
        {
            printf("我是父进程,我的pid: %d, 我正在等待子进程退出\n", getpid());
            int status = 0;
            // 阻塞等待
            pid_t res = waitpid(id, &status, 0);
            sleep(3);
            printf("等待子进程成功, 退出信号: %d, 退出码: %d\n", status&0x7F, (status>>8)& 0xFF);
        }
        return 0;
    }
    
    [lyh_sky@localhost lesson16]$ cat mycmd.c
    #include 
    #include 
    
    int main()
    {
        std::cout << "PATH:" << getenv("PATH") << std::endl;
        std::cout << "-------------------------------------------\n";
        std::cout << "MYPATH:" << getenv("MYPATH") << std::endl;
        std::cout << "-------------------------------------------\n";
    
        std::cout << "hello c++" << std::endl;
        std::cout << "hello c++" << std::endl;
        std::cout << "hello c++" << std::endl;
        std::cout << "hello c++" << std::endl;
        std::cout << "hello c++" << std::endl;
        std::cout << "hello c++" << std::endl;
        std::cout << "hello c++" << std::endl;
        std::cout << "hello c++" << std::endl;
        return 0;
    }
    [lyh_sky@localhost lesson16]$ export MYPATH=100
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
  • 相关阅读:
    Revit API: Pipe & Duct -管道和风管
    世界数字工厂的发展现状究竟如何?仅10%公司实施完成!
    Spring 事务 @Transactional 注解
    android中集成阿里云金融级实人认证
    第一章 STM32 CubeMX (CAN通信发送)基础篇
    线程崩溃为什么不会导致 JVM 崩溃
    Mysql基础篇(Mysql数据类型)
    HTML期末作业-基于HTML+CSS+JavaScript制作学生信息管理系统模板
    代码评审——Java占位符%n的处理
    IDEA快捷键和模板
  • 原文地址:https://blog.csdn.net/weixin_59400943/article/details/127856067