【Linux系统编程】Linux多进程介绍及使用

Linux多进程介绍及使用

1.相关的概念:

进程:

一个正在运行的程序,只要你运行了程序,那么系统中都会产生一个对应的进程,动态的概念

程序:

用编译器编译得到的二进制文件,静态的概念

相关的shell命令:

ps -elf 查看当前系统进程的状态信息

ps英文全称process state

进程的组成:

代码块(程序代码)+数据块(程序中用到的数据)+进程控制块

进程控制块(PCB-->process contrl block):指的是系统中定义的一个结构体

系统相关的头文件/usr/src/linux-hwe-5.15-headers-5.15.0-136/include/linux/sched.h

c 复制代码
struct task_struct
{
    //当./程序名运行一个程序的时候,系统里面产生对应名字的进程,并且把该进程在运行时候的状态参数全部存放到一个结构体变量中
    //这个结构体叫做struct task_struct(进程控制块 )
    //存放了跟当前进程有关的一些状态信息(比如:进程运行占用cpu百分比,内存占用情况,打开的文件信息)
    long state //保存进程的运行状态
                 -1表示没有运行
                 0表示正在运行
                 >0表示暂停
    int pid;  //进程的ID号
    long  prority //进程的优先级

}

父进程:

\quad 哪个主函数调用fork创建子进程,该程序就是父进程

c 复制代码
int main()  //父进程
{

    fork();  //子进程
}

孤儿进程(僵尸进程):

\quad 父进程优先于子进程退出了,导致子进程的资源没有回收,这种现象就叫做孤儿进程

2.linux提供跟进程有关的接口函数

(1)进程的创建(创建子进程)

c 复制代码
#include <unistd.h>
pid_t fork();
    返回值:   >0  表示父进程,此时返回值中存放的是子进程的id号
              ==0 表示子进程
              -1  表示失败

fork函数的特点:

①一次调用,两次返回,分别表示父子进程
示例代码:fork返回值的理解
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    pid_t pid;
    printf("程序开始运行\n");
    pid = fork();
    if (pid > 0) //父进程
    {
        printf("父进程中的ID: %d\n",pid);
        while(1)
        {
            printf("父进程循环\n");
            sleep(1);
        }
    }
    else if (pid == 0) //子进程
    {
        printf("子进程中的ID: %d\n",pid);
        while(1)
        {
            printf("子进程循环\n");
            sleep(1);
        }
    }
    else
    {
        printf("fork失败\n");
        return -1;
    }

    return 0;
}

/*
执行结果:
    程序开始运行
    父进程中的ID: 3404
    父进程循环
    子进程中的ID: 0
    子进程循环
    父进程循环
    子进程循环
    父进程循环
    子进程循环
    ...
 */
② fork创建子进程的时候,会给子进程分配独立的内存空间,同时子进程会复制父进程的所有资源(会复制fork之前的变量(全局和局部))
示例代码:子进程创建成功会复制父进程的资源
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

/*
    子进程创建成功,子进程拥有独立的内存空间
    讨论研究:
        子进程从它父进程那里里拷贝了什么,拷贝的特点
        特点1:全局变量,子进程会备份一个,跟父进程的全局变量同名,但是相互独立
        特点2:局部变量,子进程会备份一个,跟父进程的局部变量同名,但是相互独立
*/

// 定义全局变量
int g_num = 10;

int main()
{
    pid_t pid;
    int m = 100;
    printf("程序开始运行\n");
    pid = fork();
    if (pid > 0) //父进程
    {
        // 父进程修改全局变量,子进程不会受影响
        g_num = 20; 
        m = 200;
        printf("父进程中的ID: %d\n",pid);
        while(1)
        {
            printf("父进程循环 全局变量,g_num=%d\n", g_num);
            printf("父进程循环 局部变量,m=%d\n", m);
            sleep(1);
        }
    }
    else if (pid == 0) //子进程
    {
        // 子进程修改全局变量,父进程不会受影响
        g_num = 30;
        m = 300;
        printf("子进程中的ID: %d\n",pid);
        while(1)
        {
            printf("子进程循环 全局变量,g_num=%d\n", g_num);
            printf("子进程循环 局部变量,m=%d\n", m);
            sleep(1);
        }
    }
    else
    {
        printf("fork失败\n");
        return -1;
    }

    return 0;
}

/*
执行结果:
    程序开始运行
    父进程中的ID: 3548
    父进程循环 全局变量,g_num=20
    父进程循环 局部变量,m=200
    子进程中的ID: 0
    子进程循环 全局变量,g_num=30
    子进程循环 局部变量,m=300
    父进程循环 全局变量,g_num=20
    父进程循环 局部变量,m=200
    子进程循环 全局变量,g_num=30
    子进程循环 局部变量,m=300
 */

fork底层原理分析:

(2)进程的退出和回收

c 复制代码
结束进程:
void exit(int status);
void _exit(int status);

exit和_exit的区别:

  • exit结束进程的时候会刷新缓冲区,但是_exit()不会刷新缓冲区
  • 正常情况下:\n和return语句都能刷新缓冲区
示例代码:exit和_exit的区别
c 复制代码
#include "myhead.h"

/*
    区别:
        exit会刷新缓冲区
        _exit不会刷新缓冲区
    相同:都可以结束某个进程
*/

int main()
{
    printf("hello world!");
    //exit(0); //结束当前进程,会刷新缓冲区,因此打印语句可以见到
    _exit(0);  //结束当前进程,不会刷新缓冲区,因此打印语句不能见到
}

return和exit的区别:

  • 区别一:return 是关键字,exit()是函数
  • 区别二:return结束函数,返回返回值

exit()结束整个进程

示例代码:return和exit的区别
c 复制代码
#include "myhead.h"

/*
    区别:
        exit是个函数
        return是个关键字
        
        exit结束整个进程
        return结束当前函数调用,返回某个结果给到调用者
*/

int fun()
{
    printf("fun这个函数被调用了!\n");
    //exit(0);  //结束整个进程 ,当执行在exit函数时整个进程退出了,不会执行后面的语句
    return 0;   //fun有个返回值,是0
}

int main()
{
    printf("主函数开始运行!\n");
    fun();
    printf("调用fun完毕,主函数准备退出!\n");
    return 0;  
}

进程的回收wait

c 复制代码
#include <sys/wait.h>
pid_t wait(int *stat_loc);
   返回值:成功 回收到的那个子进程的id号
                失败 -1
   参数(重点):stat_loc --》存放进程退出时候的状态信息
              进程的退出值(exit/_exit圆括号里面的整型参数)仅仅只是状态信息的一部分,状态信息还包含其它内容(子进程是正常退出还是异常退出,是被哪个信号终止的)

特点:阻塞父进程,等待子进程的退出

补充:

  • kill -9 进程的ID号 //给指定的进程发送9号信号
  • kill -l //罗列linux系统中所有的信号
示例代码:进程的退出与回收wait
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    pid_t pid;
    int status;
    printf("程序开始运行\n");
    pid = fork(); // 创建子进程
    if (pid > 0) //父进程
    {
        printf("父进程执行,父进程id=%d,子进程id=%d\n", getpid(), pid);
    }
    else if (pid == 0) //子进程
    {
        printf("子进程执行,子进程id=%d\n",getpid());
        exit(5); //退出子进程 ,如不加exit退出子进程,运行会提示子进程异常退出
    }
    else
    {
        perror("fork失败\n");
        return -1;
    }
    printf("父进程准备退出\n");

    // 回收新建的子进程
    pid_t ret = wait(&status);
    //判断子进程是正常退出还是异常退出
    if(WIFEXITED(status))
    {
        printf("子进程是正常退出的,退出值是: %d!\n",WEXITSTATUS(status));
        printf("%d\n",status);
    }
    else
        printf("子进程是异常退出的!\n");
    printf("父进程回收子进程成功,回收的子进程ID是: %d\n",ret);
    return 0;
}

/*
执行结果:
// 屏蔽exit()函数的情况
    程序开始运行
    父进程执行,父进程id=3323,子进程id=3324
    父进程准备退出
    子进程执行,子进程id=3324
    父进程准备退出
    子进程是异常退出的!
    父进程回收子进程成功,回收的子进程ID是: -1
    子进程是正常退出的,退出值是: 0!
    0
    父进程回收子进程成功,回收的子进程ID是: 3324

// 未屏蔽exit()函数的情况  
    程序开始运行
    父进程执行,父进程id=3339,子进程id=3340
    父进程准备退出
    子进程执行,子进程id=3340
    子进程是正常退出的,退出值是: 5!
    1280
    父进程回收子进程成功,回收的子进程ID是: 3340
 */

进程的回收waitpid

c 复制代码
pid_t waitpid(pid_t pid, int *stat_loc, int options);
  返回值:成功 回收到的那个子进程的id号
              失败 -1
    参数:pid --》  <  -1  回收进程组id是pid绝对值中的某个进程
                           比如: waitpid(-10000,) 回收进程组ID是10000的这个组里面的某个进程
                    == -1  回收任意一个进程
                           比如: waitpid(-1,);
                    == 0   回收本进程组中的某个进程-跟主函数在同一组内的进程
                           比如: waitpid(0) 
                    >  0   指定回收进程id是pid的这个进程
                           比如:  waitpid(10000,); 回收id是10000的这个进程
          options --》 WNOHANG  非阻塞等待,父进程在退出的时候,如果子进程还没有退出,那么父进程不会阻塞,也不会去回收子进程,直接退出
                              0        阻塞等待
    补充:进程组就是多个进程组成的一个集合
              linux允许多个进程加入到某个组里面,形成了一个小团队--》进程组
示例代码:waitpid指定回收子进程
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

/*
    waitpid指定回收子进程
*/

int main()
{
    pid_t id,id1,id2;
    pid_t ret;
    int i;
    int status;
    //先生第一个
    id=fork();
    if(id>0) //父进程
    {
        //生第二个
        id1=fork();
        if(id1==0) //第二个子进程
        {
            printf("扫描漏洞!\n");
            while(1)
            {
                // 此时父进程会阻塞在这里,等待子进程结束
            }
            exit(2);
        }
        else if(id1>0) //父进程
        {
            //生第三个子进程
            id2=fork(); 
            if(id2==0) //第三个子进程
            {
                printf("清理垃圾!\n");
                exit(3);
            }
        }
    }
    else if(id==0) //第一个子进程
    {
        printf("杀毒!\n");
        exit(1);
    }
    
    printf("父进程执行软件升级!\n");
    //回收三个子进程-->假设我按照 2  3  1来回收
    ret=waitpid(id1,&status,0);  // 0:阻塞等待  WNOHANG:非阻塞
    printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
    ret=waitpid(id2,&status,0);
    printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
    ret=waitpid(id,&status,0);
    printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
    return 0;
}

/*
执行结果:
    杀毒!
    父进程执行软件升级!
    扫描漏洞!
    清理垃圾!
    // 此时父进程会阻塞在这里,等待子进程结束
*/
示例代码:waitpid非阻塞等待
c 复制代码
#include "myhead.h"

/*
    waitpid非阻塞等待
*/

int main()
{
    pid_t id,id1,id2;
    pid_t ret;
    int i;
    int status;
    //先生第一个
    id=fork();
    if(id>0) //父进程
    {
        //生第二个
        id1=fork();
        if(id1==0) //第二个子进程
        {
            printf("扫描漏洞!\n");
            sleep(2);
            exit(2);
        }
        else if(id1>0) //父进程
        {
            //生第三个子进程
            id2=fork(); 
            if(id2==0) //第三个子进程
            {
                printf("清理垃圾!\n");
                exit(3);
            }
        }
    }
    else if(id==0) //第一个子进程
    {
        printf("杀毒!\n");
        exit(1);
    }
    
    printf("父进程执行软件升级!\n");
    //回收三个子进程-->假设我按照 2  3  1来回收
    ret=waitpid(id1,&status,WNOHANG);
    printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
    ret=waitpid(id2,&status,0);
    printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
    ret=waitpid(id,&status,0);
    printf("我目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
    return 0;
}

具体宏定义如下:

(3)获取进程的id以及获取父进程的id

c 复制代码
获取当前进程的id号
pid_t getpid(void);
c 复制代码
获取父进程的id
pid_t getppid(void);
示例代码:创建多个子进程1
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    pid_t pid1, pid2;
    int ret, status;
    //创建两个子进程
    //错误写法1: 实际结果创建出来的不止两个子进程
    //fork();
    //fork();
    
    //错误写法2: 实际结果创建出来的不止两个子进程
    //for(int i=0; i<2; i++)
        //fork();

    pid1 = fork();
    if (pid1 > 0) //父进程
    {
        printf("This is parent process1\n");
        // 创建第二个子进程
        pid2 = fork();
        if (pid2 > 0) //父进程
        {
            printf("This is parent process2\n");
        }
        else if (pid2 == 0) //子进程2
        {
            printf("This is child process 2\n");
            exit(2);
        }
    }
    else if( pid1 == 0) //子进程1
    {
        printf("This is child process 1\n");
        exit(1);
    }

    printf("父进程执行!\n");
    //回收两个子进程
    ret=wait(&status);
    // printf("目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
    printf("目前回收的子进程是: %d,退出值: %d\n",ret, WEXITSTATUS(status));
    ret=wait(&status);
    // printf("目前回收的子进程是: %d,退出值: %d\n",ret,(status&0xff00)>>8);
    printf("目前回收的子进程是: %d,退出值: %d\n",ret,WEXITSTATUS(status));

    return 0;
}

/*
执行结果:
    This is parent process1
    This is parent process2
    父进程执行!
    This is child process 1
    This is child process 2
    目前回收的子进程是: 4978,退出值: 1
    目前回收的子进程是: 4979,退出值: 2
*/
示例代码:创建多个子进程2
c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

#define N 5

int main() {
    pid_t children[N];   // 记录子进程 PID,以便精确回收

    for (int i = 0; i < N; i++) {
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork");
            exit(1);
        } else if (pid == 0) {
            // 子进程工作
            printf("子进程 %d (PID=%d) 工作中...\n", i+1, getpid());
            sleep(1);   // 模拟工作
            exit(i+1);  // 退出码为 i+1
        } else {
            children[i] = pid;   // 父进程保存子进程 PID
        }
    }

    // 父进程回收所有子进程,并收集退出状态
    for (int i = 0; i < N; i++) {
        int status;
        pid_t pid = waitpid(children[i], &status, 0);
        if (WIFEXITED(status)) {
            printf("子进程 %d (PID=%d) 正常退出,退出码 %d\n",
                   i+1, pid, WEXITSTATUS(status));
        }
    }

    printf("所有子进程已回收,父进程结束。\n");
    return 0;
}

/*  
执行结果:
    子进程 1 (PID=3057) 工作中...
    子进程 3 (PID=3059) 工作中...
    子进程 2 (PID=3058) 工作中...
    子进程 5 (PID=3061) 工作中...
    子进程 4 (PID=3060) 工作中...
    子进程 1 (PID=3057) 正常退出,退出码 1
    子进程 2 (PID=3058) 正常退出,退出码 2
    子进程 3 (PID=3059) 正常退出,退出码 3
    子进程 4 (PID=3060) 正常退出,退出码 4
    子进程 5 (PID=3061) 正常退出,退出码 5
    所有子进程已回收,父进程结束。
*/
相关推荐
三万棵雪松2 小时前
【Linux 物联网网关主控系统-Web部分(四)】
linux·前端·物联网·嵌入式linux
宵时待雨2 小时前
linux笔记归纳1:linux初识
linux·运维·笔记
Deitymoon2 小时前
linux——线程设置分离属性
linux
|_⊙2 小时前
Linux进程(上)
linux·运维·服务器
feng_you_ying_li2 小时前
linux之git/gdb的使用与介绍
linux
FreeBuf_2 小时前
Nginx-UI 备份恢复漏洞 PoC 公开:攻击者可篡改加密备份并注入恶意配置
运维·nginx·ui
吕司2 小时前
Linux页表的概念
linux·运维·服务器
ShineWinsu2 小时前
对于Linux:环境变量的解析
linux·面试·笔试·进程·环境变量·本地变量·getenv
坚持就完事了2 小时前
Linux上编写和运行Python\Java
linux·运维·服务器