Linux-进程控制

本文详细解析了Linux进程的创建、终止机制,包括fork函数、exit和_exit的区别,以及wait和waitpid的用法。还探讨了进程替换原理和exec函数系列,展示了如何使用C语言实现一个简单的shell示例。
摘要由CSDN通过智能技术生成

目录

1.进程创建

2. 进程终止

2.1 进程退出的场景

2.2 进程常见退出方法

2.3 return返回终止

2.4 exit()和_exit()

3. 进程等待

3.1 进程等待的原因

3.2 wait​编辑

3.3 waitpid

3.4 status

4. 进程替换

4.1 替换原理

4.2 exec函数系列


1.进程创建

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

返回值自进程中返回0,父进程返回子进程id,出错返回-1

对于返回值的理解:上层概念上的理解

fork在创建子进程时,OS会做的:

  • 分配新的内存块和内核数据结构(task_struct)给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

写时拷贝 :通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本

fork函数中return id就是写时拷贝


2. 进程终止

2.1 进程退出的场景

  • 代码执行完毕,结果正确
  • 代码执行完毕,结果错误
  • 代码异常终止

2.2 进程常见退出方法

正常终止:

  • main函数返回 return 
  • 调用exit()
  • _exit()

异常终止:

  • ctrl + c 信号终止
  • kill命令 信号终止

2.3 return返回终止

return n 返回值n为0代表结果正确返回,n非0代表结果不正确,在main函数的return才具有这样的意义,函数的返回值没有

2.4 exit()和_exit()

exit是c库提供的函数,_exit是操作系统提供的函数接口

exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作

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

3. 进程等待

3.1 进程等待的原因

子进程退出,如果父进程没有作为,就会导致子进程变成僵尸进程,进而导致内存泄漏

父进程派给子进程的任务,子进程完成的怎么样了,父进程需要获取

3.2 wait

#include<sys/types.h>

#include<sys/wait.h>

pid_t wait(int* status)

等待任意一个子进程

返回值:

        成功返回被等待子进程的pid,失败返回-1

参数:

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

3.3 waitpid

pid_t waitpid(pid_t pid, int* status, int options)

wait函数的功能完善版

返回值:

        正常返回等待子进程的pid

        调用出错返回-1

        options 设置为WNOHANG时 当调用waitpid时发现没有退出的子进程可以收集返回0

参数:

        当pid设置为-1时,标识等待任意一个子进程,等同于wait

        当pid设置为指定的pid,标识等待这个pid的子进程

        status和上面一样下面会介绍

        options,为0时标识阻塞等待,父进程会被加载到阻塞队列里,wait默认也是阻塞等待

        options,为WNOHANG时标识非阻塞等待,若子进程没有结束,返回0,不予以等待 ,WNOHANG,是宏定义的值为1

3.4 status

status是以位图的形式存储信息

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特 位):

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

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        exit(1);
    }
    else if (id == 0)
    {
        //子进程
        int cnt = 3;
        while(cnt)
        {
            printf("我是子进程pid: %d ppid: %d\n",getpid(),getppid());
            sleep(1);
        }
        exit(11);
    }
    else{
        //父进程
        sleep(5);
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        printf("返回值:%d  子进程退出码: %d  终止信号:%d\n",ret, (status >> 8) & 0xFF, status & 0x7F);
        sleep(2);
//        while(1)
//        {
//            printf("我是父进程pid: %d ppid: %d\n",getpid(),getppid());
//            sleep(1);
//        }
    }
    return 0;
}

获取退出状态(status >> 8)& 0xFF

获取终止信息   status & 0x7F

也可以使用宏函数来完成获取status中的信息:

WIFEXITED(status)非0表示进程正常结束

WIFSIGNALED(status)非0表示进程信号终止

WEXITSTATUS(status)获取进程退出码

WTERMSIG(status)获取终止信号

进程的非阻塞等待,以及宏函数获取status 

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<wait.h>
#include<stdlib.h>
#include<iostream>
#include<vector>

using namespace std;
typedef void (*handler_t)(); 


void func1()
{
    printf("任务1\n");
}

void func2()
{
    printf("任务2\n");
}

std::vector<handler_t> handlers;
//加载函数
void Load()
{
    handlers.push_back(func1);
    handlers.push_back(func2);
}

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        exit(1);
    }
    else if(id == 0)
    {
        //子进程
        int cnt = 3;
        while(cnt--)
        {
            printf("子进程 pid: %d ppid: %d\n",getpid(),getppid());
            sleep(1);
        }
        exit(11);
    }
    else{
        //父进程
        int status = 0;
        int quit = 0;
        while(!quit)
        {
            pid_t ret = waitpid(id, &status, WNOHANG);

            if(ret < 0)
            {
                perror("waitpid");
                exit(1);
            }
            else if(ret == 0)
            {
                //子进程没有退出
                if(handlers.empty())
                    Load();
                for(auto iter:handlers)
                {
                    iter();
                }
                sleep(1);
            }
            else
            {
                 if(WIFEXITED(status))
                 {
                     printf("退出码: %d\n",WEXITSTATUS(status));
                 }
                 else if(WIFSIGNALED(status))
                 {
                     printf("退出信号: %d\n",WTERMSIG(status));
                 }
                 quit = 1;
            }

        }

        //printf("父进程 ret: %d 退出码: %d 退出信号: %d\n", ret, (status >> 8)&0xFF, status&0x7F);
    }


    return 0;
}

4. 进程替换

4.1 替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

4.2 exec函数系列

  • 这些函数调用成功就会加载新的程序序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

exec命名的规律:

  • l(list): 表示参数采用列表的形式,可变参数列表
  • v(vector): 表示参数采用数组的形式
  • p(path): 表示路径通过环境变量PATH获取
  • e(environ): 表示自己维护环境变量

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve

实现shell

//整体结构:创建子进程,由子进程获取指令,父进程判断完成的怎么样
//1.打印标识开头
//2.获取指令字符串
//3.分析字符串提取指令到grev[]
//4.部分指令的特殊处理,例如cd
//5.替换进程execvpe
//
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>

#define SIZE 1024
#define NUM 32

char str[SIZE];
char* _grev[NUM];
char _env[NUM][NUM];

int main()
{
    int num_env = 0;
    while(1)
    {
            //1.
            printf("[root$loadhost myshell]# ");
            fflush(stdout);
            //2.
            memset(str,SIZE,'\0');
            fgets(str, SIZE, stdin);
            int sz = strlen(str);
            str[sz - 1] = '\0';
            //3.
            _grev[0] = strtok(str, " ");
            int index = 1;
            //4.
            if(strcmp(_grev[0],"ls") == 0)
            {
                _grev[index++] = (char*)"--color=auto";
            }
            if(strcmp(_grev[0], "ll") == 0)
            {
                _grev[0] = (char*)"ls";
                _grev[index++] = (char*)"--color=auto";
                _grev[index++] = (char*)"-l";
            }
            
            while(_grev[index++] = strtok(NULL, " "));
            if(strcmp(_grev[0], "cd") == 0)
            {
                if(_grev[1]) chdir(_grev[1]);
                continue;
            }
            if(strcmp(_grev[0], "export") == 0 && _grev[1])
            {
                memcpy(_env[num_env],_grev[1],strlen(_grev[1])+1);
                putenv(_env[num_env]);
                num_env++;
                continue;
            }
        pid_t id = fork();
        if(id < 0)
        {
            perror("fork");
            exit(1);
        }
        else if(id == 0)
        {
            //child
            //5.
            execvp(_grev[0], _grev);
            exit(2);
        }
        else {
            //father
            int status = 0;
            pid_t ret = waitpid(id, &status, 0);
            if(ret < 0)
            {
                printf("等待子进程失败\n");
                exit(2);
            }
            else{
                if(WIFEXITED(status))
                {
                    printf("子进程退出码:%d\n",WEXITSTATUS(status));
                }
                else if(WIFSIGNALED(status))
                {
                    printf("子进程终止信号:%d\n",WTERMSIG(status));
                }
            }
        }
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值