嵌入式学习记录 2024.5.9(多进程编程)

一.进程,多进程编程

1.1 什么是进程

平时写的代码,叫程序,是一个进程的概念我们使用./a.out 运行这个程序的时候,运行出来的东西,就叫做进程,他是一个动态的概念

动态体现在哪里:上下文切换,内存动态申请,动态释放

1.2 什么是多进程编程

理论上来说,在一个时间单位内,单核计算机只能运行一个进程。但是这个时间单位非常的短暂,非常快速的运行到下一个时间单位之后,就会切换到另一个进程运行,由于人的感官感觉不到这么短暂的时间,从而造成一种2个进程在同时运行的假象

而每个进程都有自己的内存空间和执行环境,彼此之间相互独立。这种编程模型可以允许程序同时执行多个任务,提高系统的并发性能和效率。在多进程编程中,通常使用系统调用(如fork())来创建新的进程,并通过进程间通信来实现进程之间的数据交换和协作。

二.进程的内存管理

1.1个进程总是占据4个gb的内存空间其中,前3个gb是该进程的独立内存空间,后1个gb是所有进程共享的内存空间。

但是注意:linux系统中,进程使用的其实不是物理内存,而是虚拟内存

4个gb全是虚拟内存,这4gb的虚拟内存,是由1gb的物理内存映射出来的

怎么做到1gb物理内存映射出4gb虚拟内存

a:平时我们写代码根本用不到1gb,1gb物理内存完全够用,

b:代码内存使用情况超过了1gb,物理内存就会向磁盘借用3gb,然后将活跃的数据交换到1gb物理内存中去使用,不活跃的数据,存放在3gb磁盘中。

三.进程的组成部分和进程编号

1.组成部分

1).pcb:包含了进程的各种属性和状态信息,如进程ID、优先级、程序计数器、寄存器状态等。

2).数据段:进程运行时使用的数据,包括全局变量、堆、栈等。

3).程序段:进程的执行指令集合,通常是可执行文件的指令序列。

2.进程编号

每一个进程都有自己独立的编号,称为pid

pid的分配方式为:循序寻找未使用的pid

0 1 2 3

此时打开一个新的进程

0 1 2 3 4

此时关闭2号进程

0 1 3 4

四.特殊进程(0,1,2)

0号进程 :又叫做 idel 或者 swapper进程:0号进程只负责1个事情,打开1号进程和2号进程,然后0号进程就进入休眠状态。如果计算机中没有任何进程运行的话,就会运行0号进程。

1号进程:又称为始祖进程 或者 init进程:1号进程开启之后,处于内核态,我们称为内核态1号进程,负责获取主机的所有硬件信息并初始化,初始化完成之后。初始化完所有硬件信息之后,内核态的1号进程,就会转变成用户态的1号进程,用户态的1号进程负责:开启所有的用户进程。

2号进程:kthread进程,他只有1个工作,用来对多个进程进行调度工作

0 1 2 这3个进程本质上是一个进程,3个分身(线程)

五.进程的分类

1) 交互进程:顾名思义,一个可以用户进程正常交互的进程(可以通过标准输入流,标准输出流,标准错误流与用户进行交互)。也就是说交互进程严重依赖终端

2) 批量处理进程:与交互进程是相对的,因为不需要实时响应用户的交互需求。批量处理进程就可以在某个时间点内,批量的运行一堆进程

我们能够运行的批量处理进程,是一个后台进程:在 ./a.out后面加上&,就可以让a.out进程在后台运行

注意:后台运行的进程,标准输出流和标准错误流还是借用运行他的终端,但是他不是一个交互进程

3)守护进程:是一个不与任何用户交互,不依赖任何进程组,独立存在的,在后台持续运行的一个服务性质的进程

那么如何成为一个守护进程

① 成为一个孤儿进程,脱离终端控制(这是初步脱离终端控制)

② 成为一个新的进程组的组长

pid_t setsid(void) 功能描述:如果调用者进程不是一个进程组的组长的话,则创建一个新的进程组,并且让该进程成为新的进程组的组长

③ 切换守护进程的工作目录,到系统根目录

int chdir(const char *path); 功能描述:将当前进程的工作目录切换到 path目录 返回值:成功返回0,失败返回-1

④ 设置守护进程的掩码为 0111

⑤ 彻底脱离与终端的联系:将守护进程的标准输入,输出,错误流全都重定向到独立的文件中去

六 进程相关的shell指令

ps -ef 显示进程之间关系的指令

ps -ajx 查看进程状态的指令

ps -aux 显示进程占据cpu资源情况

向进程发送信号的指令:kill

pidof 进程名 : 获取一个进程的pid

top :显示任务管理器:动态的显示系统中的所有进程

pstree 以树状图的形式,显示系统中所有进程关系

ps ef -o pid,ppid,command  :只显示系统中所有进程的pid,ppid,和进程名

七.进程状态切换的模拟

1》运行一个死循环进程

2》键盘按 ctrl + z 中断进程的运行,这个进程就会在后台中断

此时该进程不仅中断了,还进入了后台,无法交互了

3》现在想要让他进入前台运行

在该进程之前运行的终端上,输入jobs,查看该进程的工作号

然后使用指令 fg 工作号,让该进程重新进入前台并运行

八.进程状态的切换

九.创建一个进程

1.如何打开一个新的进程实现并发:使用函数fork

pid_t fork(void);

功能描述:在当前进程中,创建一个新的进程。被创建的进程我们称为子进程,创建子进程的进程称为父进程 创建出来的子进程的特点: 1:创建出来的子进程,运行父进程fork之后的代码 2:子进程创建出来之后,会拷贝父进程的所有内存

2.如何区分fork之后,哪部分代码由父进程,哪部分代码由子进程执行

使用函数 getpid getppid 去实现

pid_t getpid(void);

功能描述:返回调用者进程的进程编号

pid_t getppid(void); 功能描述:返回调用者进程的父进程的进程编号 2:通过fork返回值去区分 其实,子进程子啊fork函数内部,就已经产生了 所以fork函数的前半段,只有父进程在运行,fork函数的后半段,由父子进程共同运行 注意,fork函数是有返回值的,返回值处于fork函数的后半段 所以fork函数返回值,由父子进程共同返回 父进程运行fork函数后半段部分:返回子进程的pid 子进程运行的fork函数后半段部分:返回0

十.孤儿进程,僵尸进程

一.孤儿进程

每一个进程占据4个gb的内存空间,当一个进程结束运行之后,理论上,要由他的父进程进行资源回收此时,如果说,一个进程还在运行,但是他的父进程已经死亡了。此时这个进程我们就称为孤儿进程在标准的linux系统里面,孤儿进程的资源,会由1号进程进行回收

注意:父进程回收死亡的子进程资源,也不是自动回收的,是要调用函数来回收的,以下2个函数可以回收子进程资源

pid_t wait(int *stat_loc);

功能描述:wait函数会获取等待回收资源的子进程的状态信息,并将获取到的状态信息保存到 stat_loc指向的内存空间中,然后回收子进程的资源. 如果不需要获取子进程死亡时候的信息的话,stat_loc参数直接传NULL或者0 问题在于:死亡的子进程状态信息怎么来? 进程main函数的返回值,或者exit函数的参数

注意:wait是一个阻塞型函数 void exit(int status); 功能描述:立刻技术当前进程,并且参数status作为该进程main函数的返回值进程返回 pid_t waitpid(pid_t pid, int *stat_loc, int options); 这个函数是wait函数的高阶自定义版本,允许自定义很多细节部分 参数pid: pid == -1: 可以回收任意子进程的资源 pid > 0:回收一个进程的资源,回收传入的pid进程的资源,但是要求该pid进程所在的进程组和父进程是一样的 pid == 0:回收任意进程组编号和父进程一样的子进程的资源 pid < -1:回收一个进程的资源,进程号为 |pid|那个进程的资源 参数options:一般有2个选项 0:waitpid函数为阻塞型函数 WNOHANG:waitpid函数为非阻塞型函数

二.僵尸进程

当子进程死亡,而父进程一直没有空去wait子进程的资源,此时该子进程资源就泄漏了,这样的子进程,我们称为僵尸进程

注意:僵尸进程是一个高危操作,尽量避免僵尸进程的出现

练习

今日作业:创建一对父子进程:

父进程负责向文件中写入 长方形的长和宽

子进程负责读取文件中的长宽信息后,计算长方形的面积(int)

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
#include <stdio.h>
// 今日作业:创建一对父子进程:
// 父进程负责向文件中写入 长方形的长和宽
// 子进程负责读取文件中的长宽信息后,计算长方形的面积 int
struct rectangle
{
    int length;
    int width;
} rec;
int main(int argc, const char *argv[])
{
    int retval = fork();
    if (retval > 0)
    {
        FILE *fd = fopen("test.txt", "w");
        printf("请输入长方形的长");
        scanf("%d", &rec.length);
        printf("请输入长方形的宽");
        scanf("%d", &rec.width);1
        while (getchar() != '\n')
            ;
        fwrite(&rec.length, 4, 1, fd);
        fwrite(&rec.width, 4, 1, fd);
        fclose(fd);
        wait(0);
    }
    else if (retval == 0)
    {
        sleep(3);
        FILE *fd = fopen("test.txt", "r");
        fread(&rec.length, 4, 1, fd);
        fread(&rec.width, 4, 1, fd);
        int s = rec.length * rec.width;
        printf("长方形的面积为;%d\n", s);
        fclose(fd);
    }
    else
    {
        perror("fork");
    }
    return 0;
}
请输入长方形的长2
请输入长方形的宽3
长方形的面积为;6
[1] + Done                       "/usr/bin/gdb" --interpreter=mi --tty=${DbgTerm} 0<"/tmp/Microsoft-MIEngine-In-3gt1jwk4.knx" 1>"/tmp/Microsoft-MIEngine-Out-agsb05vn.az0"

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值