【Linux】进程管理

本文详细介绍了进程的创建过程,包括fork()操作和地址空间的复制;探讨了进程的等待机制,如阻塞和非阻塞等待,以及status位图的使用;同时涵盖了进程替换,涉及exec*系列函数和高级语言程序的替换方法。
摘要由CSDN通过智能技术生成

目录

一、进程创建

二、进程等待

1. 阻塞等待

2. 非阻塞等待

3. status位图

三、进程替换

1. exec* 系列函数

2. 高级语言程序替换


一、进程创建

进程是操作系统进程资源分配的最小单位,每一个进程都有自己独立的PID,以及进程控制块PCB。

父进程通过 fork() 创建子进程,子进程的ppid == 父进程pid,父子进程互不干扰。

创建子进程时,操作系统的工作:

  1. 复制主进程的地址空间,包括代码段、数据段、堆、栈等,通过页表的复制机制完成,即操作系统会将父进程的页表复制一份给子进程,在此之后,父子进程共享同样的内存页,虚拟内存页,并且这些内存页都使用写时拷贝技术进行保护,减小内存开销。
  2. 设置子进程唯一标识符PID,复制父进程的文件描述符表,设置父子进程关系,设置子进程的父进程标识PPID。
  3. 初始化子进程的进程控制块PCB,操作系统为子进程分配一个进程控制块,该控制块用来保护进程的状态、优先级、寄存器状态以及其他进程相关的信息。
  4. 设置子进程的初始状态,操作系统将子进程的初始状态设置为就绪态,以便在适当的时候可以运行。

不同系统中对进程设置有不同的状态,Linux系统的进程状态分为:运行态(R)、僵尸态(Z)、暂停态(T)、睡眠态(S)、深度睡眠状态(D)、死亡态(X)。

# 查看进程状态
root@ubuntu:/var/lib/mysql/d1_mytest# ps ajx | head -1 && ps ajx | grep mysql
  PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
     1   1276   1275   1275 ?            -1 Sl     123   0:26 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid
  2620   2989   2989   2620 pts/5      2989 S+    1000   0:00 mysql -u ljc -p
  3584   7996   7995   3403 pts/1      7995 S+       0   0:00 grep --color=auto mysql
root@ubuntu:/var/lib/mysql/d1_mytest# 

可以通过 kill 控制进程的执行,kill 命令的本质就是向目标进程发送信号。

  • kill -9   PID : 杀死进程
  • kill -19 PID : 暂停进程
  • kill -29 PID : 继续进程

进程状态带 + 号,代表的是前台进程,否则是后台进程。

前台进程在终端运行,后台进程不受终端控制。

二、进程等待

1. 阻塞等待

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main()  {
    pid_t id = fork();
    if (id == 0) {
        int cnt = 5;
        while (cnt) {
            printf("子进程:%d, 父进程:%d, cnt = %d\n", getpid(), getppid(), cnt--);
            sleep(1);
        }
        exit(10);
    }
    //父进程
    int status = 0;    // 保存退出码
    pid_t ret = waitpid(id, &status, 0);    //option为 0 代表阻塞等待
    if (id > 0) {
        printf("wait success: %d, sign: %d, exit code: %d\n", ret, (status & 0x7F), (status>>8) & 0xFF);
    }
    return 0;
}

2. 非阻塞等待

  • 非阻塞等待:子进程未退出,父进程检测之后立即返回
  • 非阻塞等待的意义:不用占用父进程的所有精力,可以在等待期间执行自己的任务
  • WIFEXITED(status):是否正常退出,若正常退出则返回值非零
  • WEXITSTATUS(status):提取子进程退出码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
 
#define NUM 10
 
typedef void (*func_t)();   //函数指针
 
func_t handlerTask[NUM];
 
//测试样例
void Task1() {
    printf("handler task1\n");
}
 
void Task2() {
    printf("handler task2\n");
}
 
void Task3() {
    printf("handler task3\n");
}
 
//给父进程装载任务 
void LoadTask() {
    memset(handlerTask, 0, sizeof(handlerTask));
    handlerTask[0] = Task1;
    handlerTask[1] = Task2;
    handlerTask[2] = Task3;
}
 
int main() {
    pid_t id = fork();  //返回的本质就是写入, 写入时发生写时拷贝
    if (id < 0) {
        printf("folk error\n");
        return 1;
    }
    else if (id == 0) {
        int cnt = 5;
        while (cnt) {
            printf("我是子进程: %d, 父进程: %d, cnt = %d\n", getpid(), getppid(), cnt--);
            sleep(1);
            // int* p = NULL;
            // *p = 100;       //野指针报错, 退出信号为11
        }
        exit(10);
    }
    LoadTask();
    int status = 0;     //不是被整体使用的, 有自己的位图结构
    //非阻塞轮旋方式等待
    while (1) {
        pid_t ret = waitpid(id, &status, WNOHANG);  //非阻塞等待: 子进程未退出, 父进程检测之后立即返回
        if (ret == 0) {
            //子进程未退出, waitpid等待失败, 仅仅是检测到子进程状态未退出
            for (int i = 0; handlerTask[i] != NULL; ++i)
                handlerTask[i]();
        }
        //等待子进程退出成功
        else if (ret > 0) {
            //是否正常退出
            if (WIFEXITED(status)) {
                //正常退出, WIFEXITED()返回值为!0
                //判断子进程运行结果是否正确
                printf("exit_code: %d\n", WEXITSTATUS(status));    
            }
            else {
                //异常退出,被信号杀死
                printf("child process not normal\n");
            }
            break;
        }
        else {
            // waitpid调用失败
            printf("waitpid call failed\n");
            break;
        }
        sleep(1);
    }
    return 0;       
}

3. status位图

  • status退出信号是一个32bit位的位图,只有低16个bit位存储退出信息
  • (status & 0x7F) 为终止信号, (status>>8) & 0xFF) 为退出状态
  • 异常退出,被信号杀死
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main() {
    pid_t id = fork();
    if (id == 0) {
        int cnt = 5;
        while (cnt) {
            printf("子进程:%d, 父进程:%d, cnt = %d\n", getpid(), getppid(), cnt--);
            sleep(1);
        }
        int a = 10;
        a /= 0;         //除 0 异常
        exit(10);
    }
    //父进程
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);    //option为 0 代表阻塞等待
    if (id > 0) {
        printf("wait success: %d, sign: %d, exit code: %d\n", ret, (status & 0x7F), (status>>8) & 0xFF);
    }
    return 0;
}

三、进程替换

1. exec* 系列函数

进程替换:将指定进程的代码加载到指定位置,覆盖自己的代码和数据。

加载器的底层接口,可替换任何后端语言的可执行程序:

  • int execl(const char* path, const char* arg, ...),可变参数列表以NULL结尾
  • int execlp(const char* file, const char* arg, ...),可自动在环境变量中寻找路径
  • int execv(const char* path, char* const arg[]),可变参数数组以NULL结尾
  • int execvp(const char* file, char* const arg[])
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
 
int main() {
    printf("process is running\n");
    pid_t id = fork();
    if (id == 0) {
        sleep(1);
        //execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL);
        //execlp("ls", "ls", "-a", "-l", "--color=auto", NULL);
        char* const arg[] = {"ls", "-a", "-l", "--color=auto", NULL};
        //execv("/usr/bin/ls", arg);
        execvp("ls", arg);
        exit(10);   //若exc*调用成功,则此句代码被替换
    }
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    if (ret > 0) {
        printf("wait success, sig = %d, exit code = %d\n", (status & 0x7F), (status>>8) & 0xFF);
    }
    printf("process is running done\n");
    return 0;       
}

execve是系统调用,其他都是封装:

  • int execle(const char* path, const char* arg, ... , char* const envp[]),可传环境变量
  • int execve(const char* path, char* const arg[], char* const envp[])
  • int execvpe(const char* file, char* const arg[], char* const envp[])

2. 高级语言程序替换

#include <stdio.h>
#include <stdlib.h>
 
int main() {
    printf("C语言程序\n");
    printf("C语言程序\n");
    printf("PATH: %s\n", getenv("PATH"));
    printf("PWD: %s\n", getenv("PWD"));
    printf("MYENV: %s\n", getenv("MYENV"));
    printf("C语言程序\n");
    printf("C语言程序\n");
    exit(15);
}

// 创建一个test.c文件并编译 gcc -o test test.c
#!/bin/python
print('python process')
print('python process')
print('python process')
print('python process')
print('python process')

# 创建一个py_test.py文件,chmod u+x py_test.py
# 用进程替换这个程序,并打印环境变量
// 进程替换

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
 
int main() {
    printf("process is running\n");
    pid_t id = fork();
    if (id == 0) {
        sleep(1);
        putenv((char*)"MYENV=123456");     //添加自定义环境变量
        extern char** environ;
        execle("./test", "test", NULL, environ);
        exit(10);   //若exc*调用成功,则此句代码被替换
    }
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    if (ret > 0) {
        printf("wait success, sig = %d, exit code = %d\n", (status & 0x7F), (status>>8) & 0xFF);
    }
    execl("./py_test.py", "py_test.py", NULL);
    printf("process is running done\n");
    return 0;       
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AllinTome

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值