Linux-进程控制

目录

1.进程创建

[2. 进程终止](#2. 进程终止)

[2.1 进程退出的场景](#2.1 进程退出的场景)

[2.2 进程常见退出方法](#2.2 进程常见退出方法)

[2.3 return返回终止](#2.3 return返回终止)

[2.4 exit()和_exit()](#2.4 exit()和_exit())

[3. 进程等待](#3. 进程等待)

[3.1 进程等待的原因](#3.1 进程等待的原因)

[3.2 wait​编辑](#3.2 wait编辑)

[3.3 waitpid](#3.3 waitpid)

[3.4 status](#3.4 status)

[4. 进程替换](#4. 进程替换)

[4.1 替换原理](#4.1 替换原理)

[4.2 exec函数系列](#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比特 位):
cpp 复制代码
#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

cpp 复制代码
#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

cpp 复制代码
//整体结构:创建子进程,由子进程获取指令,父进程判断完成的怎么样
//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];

int main()
{
    while(1)
    {
            //1.
            printf("[root$hostlaod]# ");
            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;
            }
        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;
}

相关推荐
秋风起,再归来~10 分钟前
【Linux庖丁解牛】—Linux基本指令(中)!
linux·指令
Eternal-Student21 分钟前
预处理、编译、汇编、链接
linux·汇编·windows
sp_wxf1 小时前
Stream流
linux·服务器·windows
LYK_HAHA1 小时前
centos常用知识和命令
linux·运维·centos
PythonFun2 小时前
自建RustDesk服务器:详细步骤与操作指南
运维·服务器
Android技术栈2 小时前
鸿蒙开发(NEXT/API 12)【管理应用与Wear Engine服务的连接状态】手机侧应用开发
服务器·harmonyos·鸿蒙·鸿蒙系统·openharmony
可涵不会debug2 小时前
【Linux】信号知识三把斧——信号的产生、保存和处理
linux·运维·信号
笑的像个child2 小时前
使用树莓派搭建音乐服务器
服务器·树莓派·navidrome
facaixxx20242 小时前
京东云主机怎么用?使用京东云服务器建网站(图文教程)
运维·服务器·京东云
jyan_敬言2 小时前
【Linux】Linux命令与操作详解(一)文件管理(文件命令)、用户与用户组管理(创建、删除用户/组)
linux·运维·服务器·c语言·开发语言·汇编·c++