Linux中有关进程相关的函数

目录

1.创建进程

fork()函数

2.终止进程

exit()函数

3.孤儿进程

4.僵尸进程

5.进程回收

wait函数

waitpid函数


进程是正在执行程序的实例,是操作系统进行资源分配和调度的基本单位。在内核看来,进程是一个实体,内核需要在他们之间共享各种计算机资源。

1.创建进程

创建进程的方式:

系统初始化:启动操作系统时会创建若干个进程

用户请求创建:例如双击图标启动程序

系统调用创建:一个运行的进程可以发出系统调用创建新的进程帮助完成工作

在这里,我们使用的是第三个创建进程的方法。

fork()函数

函数描述:

创建进程,新创建的是当前进程的子进程。

函数原型:

pid_t fork(void);

函数返回值:

成功:

父进程:返回新创建的子进程ID

子进程:返回0

失败:

返回-1,子进程不会被创建

fork函数会创建一个子进程,父进程的内容会复制到子进程的进程空间中,包括父进程的数据段和栈堆段,并且和父进程共享代码段,所以成功后父子进程都会停留在了进程创建函数fork()上,因此,fork()函数在父子进程中都会返回,两个返回值是不同的。

父子进程间遵循读时共享写时复制的原则。现在Linux内核在fork()函数时往往在创建子进程时并不立即复制给父进程的数据段和栈堆段,而是当子进程修改这些数据内容时复制操作才会发生,内核才会给子进程分配进程空间,将父进程的内容复过来,然后继续后面的操作,这样的实现更加合理。对于那些只是为了复制自身完成的一些工作的进程来说,这样做的效率会更高,这也是现代操作系统的一个重要概念------"写时复制"的一个重要体现。

cpp 复制代码
int pid=fork();
if(pid==0){
    printf("child pid=%d,ppid=%d\n",getpid(),getppid());
}
if(pid>0){
    printf("parent pid=%d,ppid=%d\n",getpid(),getppid());
}
while(1);

getpid是获取当前进程的进程ID的函数,getppid是获得当前进程的父进程的进程ID。通过打印,可以看到创建了一个子进程。

父进程的局部变量、全局变量、堆区空间是不共享的,父子进程打印变量的地址是相同的(该地址是虚拟地址空间),但是他们指向的物理空间是不同的。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int global_var = 10;

void func(int param) {
    char* str = (char*)malloc(10);
    int local_var = 20;
    int pid = fork();
    if (pid == 0) {
        // 子进程
        global_var++;
        local_var++;
        sprintf(str, "child");
        printf("子进程:global_var = %d, local_var = %d, str = %s\n", global_var, local_var, str);
    } else if (pid > 0) {
        // 父进程
        sleep(1);
        printf("父进程:global_var = %d, local_var = %d, str = %s\n", global_var, local_var, str);
    }
    free(str);
}

int main() {
    func(5);
    return 0;
}

在上述代码中,由于子进程对全局变量、局部变量以及堆区的内容进行了写操作,就会触发"写时复制"的机制,会复制一个副本。所以子进程输出的结果就上面图中的结果,而父进程没有进行写操作,读取到的就是原始数据。而对于str,因为它在堆区上且在子进程修改时触发了写时复制,父进程这边的str所指向内存空间实际上并未有效写入内容,其内容是不确定的。

父子进程间文件共享,执行fork子进程会获得父进程所有的文件描述符分副本,这些副本的创建类似于dup(),同一文件描述符在父子进程中对应的是相同的文件。

2.终止进程

进程终止的方式:

1.正常退出

1.从main函数返回

2.调用exit()或者_exit(),exit()是库函数,_exit()是系统调用,程序一般不直接调用_exit(),而是调用库函数exit().

2.异常退出

被信号终止

exit()函数

函数描述

结束进程

头文件:

#include<stdlib.h>

函数原型

void exit(int status);

函数参数:

进程的退出状态,0表示正常退出,非0值表示因为异常退出,保存在全局变量?中,?保存的是最近一次运行的进程的返回值,返回值有以下三种情况。

1.程序中的main函数运行结束,$>保存main函数的返回值

2.程序运行中调用exit()函数结束运行,$?中保存exit函数的参数

3.程序异常退出$?中保存异常出错的错误号

3.孤儿进程

父进程先于子进程结束,则称子进程为孤儿进程,变成孤儿进程后会有一个专门用于回收的init进程成为它的父进程,称init进程领养孤儿进程。

cpp 复制代码
int pid=fork();
if(pid==0){
    while(1){
        printf("child pid=%d ppid=%d\n",getpid(),getppid());
        sleep(1);    
    }
}
sleep(10);
printf("parent pid =%d\n",getpid());

4.僵尸进程

进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成了僵尸进程。

僵尸进程不能使用kill命令清除掉。因为kill命令只是用来终止进程的,而僵尸进程已经被终止。可以杀死它的父进程,让init进程变成它的父进程,init进程可以回收它。

5.进程回收

一个进程在终止时会关闭所有的文件描述符,释放在用户空间分配的内存,但是它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出的状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这个信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell是它的父进程,当它终止时shell调用wait或waitpid得到它的退出状态同时清除掉这个进程。

wait函数

函数描述:

父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:

1.阻塞带带子进程退出

2.回收子进程残留资源

3.获取子进程结束状态(退出原因)

头文件

#include<sys/type.h>

#include<sys/wait.h>

函数原型

pid_t wait(int* status)

函数参数

status为传出参数,用于保存进程的退出状态

函数返回值:

成功:返回清理掉的子进程id

失败:返回-1

当进程终止时,操作系统的隐式回收机制会:

1.关闭掉所有文件的文件描述符

2.释放用户空间、分配的内存。内核的PCB仍然存在。其中保存该进程的退出状态。(正常终止->退出值;异常终止->终止信号)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
    pid_t pid;
    int status;
    // 创建子进程
    pid = fork();
    if (pid == 0) {
        // 子进程
        printf("子进程正在运行...\n");
        // 模拟子进程的工作,这里简单地睡眠2秒
        sleep(2);
        printf("子进程结束\n");
        // 子进程正常退出,返回状态码为0
        return 0;
    } else if (pid > 0) {
        // 父进程
        printf("父进程正在等待子进程结束...\n");
        // 调用wait函数等待子进程结束
        pid_t wpid = wait(&status);
        if (wpid == -1) {
            // 处理错误情况,如没有子进程等
            perror("wait");
            return 1;
        }
        // 检查子进程的退出状态
        if (WIFEXITED(status)) {
            int exit_status = WEXITSTATUS(status);
            printf("子进程正常退出,退出状态码为 %d\n", exit_status);
        } else if (WIFSIGNALED(status)) {
            int term_sig = WTERMSIG(status);
            printf("子进程被信号 %d终止\n", term_sig);
        }
        printf("父进程继续执行...\n");
    } else {
        // 处理fork错误情况
        perror("fork");
        return 1;
    }
    return 0;
}

可以使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程的具体原因。宏函数可以分成下面三组:

cpp 复制代码
1.WIFEXITED(status);//为真->进程正常结束
  WEXITSTATUS(status);//如上宏为真,获取机场南退出状态(exit的参数)
2.WIFIGNALED(status);//为真->进程异常终止
  WTERMSIG(status);//如上宏为真,得是进程指针的那个信号的编号
3.WIFSTOPPED(status);//为真->进程出于暂停状态
  WSTOPSIG(status);//如上宏为真,取得使进程暂停的那个信号的编号
  WIFCONTINUED(status);//如上宏为真,进程暂停后已经继续运行

waitpid函数

函数描述:

作用同wait,但是可以指定进程id为pid的进行清理,可以不阻塞。

头文件:

#include<sys/type.h>

#include<sys/wait.h>

函数原型:

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

函数参数:

参数pid:

pid>0:回收指定ID的子进程

pid=-1:回收任意子进程(相当于wait)

pid=0:回收和当前调用进程(父进程)一个组的任一子进程

pid<0:设置负的进程组id,回收指定进程组内的任意子进程

函数返回值:

成功:返回成功清理掉的子进程id

失败:返回-1

参数3为WNOTHANG,且子进程正在运行,返回0

注意:一次wait或者waitpid调用只能清理掉一个子进程,清理多个子进行应使用循环

cpp 复制代码
int pid;
int wpid;
int i;
for(i=0;i<10;i++ ){
    pid==fork();
    if(pid==0){
        break;    
    }
    if(i==2){
        wpid=pid;
        printf("i = 2 process id =%d\n",wpid);    
    }
}
if(i==10){
    printf("parent wpid=%d\n",wpid);
    int ret=waitpid(wpid,NULL,0);
    printf("ret =%d\n",ret);
    while(1);
}else{
    return 0;
}

上述代码是清除掉第三个子进程的信息。

相关推荐
奔跑的废柴7 分钟前
Jenkins学习(B站教程)
运维·学习·jenkins
Tee xm8 分钟前
清晰易懂的 Jenkins 安装与核心使用教程
linux·windows·macos·ci/cd·jenkins
曹瑞曹瑞14 分钟前
Linux制作deb安装包
linux·运维
YZF_Kevin18 分钟前
centos安装dashboard详细步骤
linux·运维·centos
kobe_OKOK_28 分钟前
CentOS 部署 Nodejs
linux·运维·centos
如若12329 分钟前
《在 Ubuntu 22.04 上安装 CUDA 11.8 和 Anaconda,并配置环境变量》
linux·运维·ubuntu
源代码•宸1 小时前
Visual Studio Code SSH 连接超时对策( keep SSH alive)
运维·服务器·ide·经验分享·vscode·ssh
zyx没烦恼1 小时前
Linux 下 日志系统搭建全攻略
linux·服务器·开发语言·c++
Tee xm2 小时前
清晰易懂的 Flutter 开发环境搭建教程
linux·windows·flutter·macos·安装
不摆烂选手2 小时前
Ubuntu之Makefile入门
linux·ubuntu·makefile·正点原子imx6ull学习笔记