进程概念

冯诺依曼

核心
所有硬件都是围绕内存工作的,所有数据必须先加载到内存,CPU 才能访问。
外设:
输入设备:键盘、鼠标、硬盘(负责把数据传给内存)
输出设备:显示器、硬盘(负责从内存拿数据)
内存:临时数据中转站(速度快、断电丢失)
CPU:运算核心(只和内存交互)
IO:输入 / 输出,外设 ↔ 内存 的数据交互
数据流:外设 → 内存 → CPU → 内存 → 外设
存储分级与IO效率:寄存器 > 缓存 > 内存(瓶颈) > 硬盘(速度递减,容量递增)

操作系统

管理软硬件
提供稳定安全的运行环境

管理

先描述
用结构体(如 task_struct)把资源的属性写清楚
再组织
用链表 / 队列把所有结构体管理起来
例子:管理进程 → 先定义 PCB 结构体 → 用链表串起所有 PCB

库函数与系统调用接口

库函数内部是封装了系统调用接口的
分支主题

进程概念

原因

当程序运行时,要从硬盘加载到内存当中,操作系统为了更方便的管理每个程序的运行,就要首先描述出每一个进程的相关信息,所以有了进程控制块,而Linux中的PCB就是task_struct

描述信息

内存 mm_struct
** 文件** files_struct
(打开的文件存储在指针数组)struct file * file_arrays[]
上下文数据 CPU 寄存器数据(进程切换时保存 / 恢复)

信号
信号的阻塞位图
sigset_t blocked
信号的处理方法
struct sigaction

进程控制

进程创建

fork

复制
以父进程为模板,为子进程创建PCB,子进程和父进程共享代码和数据写时拷贝
返回值
父进程返回子进程的pid
子进程返回0
失败返回-1

vfork

共享同一个虚拟地址空间

创建一个进程的流程

  1. 找到父进程的PCB对象,malloc一个PCB来存放子进程,用父进程的PCB初始化子进程,子进程的PCB指向父进程的代码和数据,子进程和父进程都加入调度队列开始排队等待调度
  2. 当有一方要改变数据时,就会触发写时拷贝

进程终止

终止的几种情况

正常终止,结果正确:代码正常执行完毕,正确退出码是0
正常终止,结果不正确:代码也正常执行结束了,但是不在合适的地点退出,退出码不为0
异常:代码没有正常执行结束,可以使用**echo $?**来查看
exit code:当进程收到信号而退出后,可以父进程可以通过回收资源收到退出码,以查看子进程的退出情况

终止的操作对比

exit和_exit
_exit是系统调用,
exit:_exit+刷新缓冲区+关闭文件
main 的 return
return n 等价于 exit (n)
其他return :进程还在跑

进程终止系统的行为

  1. 根据被终止进程的标识,操作系统会从它的PCB集合中找到该进程的PCB,读取该进程的状态
    2. 如果该进程处于执行状态,会立即停止执行,并修改状态标识,表示这个进程被终止
    3. 回收进程的资源
    4. 保留pcb 等待父进程回收
    5. 从运行队列中移除

进程等待

原因

防止内存泄漏,子进程退出,父进程不回收(waitpid),就会导致僵尸状态的出现

僵尸进程:

子进程异常终止,父进程并没有调用wait/waitpid,子进程的进程描述符等资源仍在系统中占用资源,那么此时子进程就变成了僵尸进程Z

进程等待的方式

wait:pid_t wait(int *status);
waitpid: pid_t waitpid(pid_t pid, int *status, int options);
option为0,表示阻塞等待,WNOHANG表示非阻塞等待
status是一个输出型参数,0-7是退出信号,8-15是位图
signal(退出信号)和exit code(位图)

阻塞等待和非阻塞等待

阻塞等待是停在函数调用位置等待,直到回收资源成功
非阻塞等待是,每到接口处就检测,搭配循环使用,可以做其他事

进程状态

创建,就绪,运行,阻塞,终止状态
每一种状态就是宏定义
状态变换本质就是修改宏的值,放入不同的队列

就绪状态

当进程的时间片用完之后,就会从CPU上剥离下来,此时会保存寄存器的代码和数据,存储到PCB当中,紧接着把进程的PCB加入到就绪队列当中,等待下一次调度运行

挂起状态

当进程在阻塞状态时,如果内存资源不足,会暂时把阻塞进程的代码和数据置换到磁盘的swap分区中,当进程被调度的时候再把数据加载进来
转换流程
分支主题

僵尸进程

产生原因
子进程终止,父进程不回收子进程资源
危害
子进程的数据没有被回收,就会内存泄漏
避免

  1. 进程等待
    主动调用接口,waitpid进行阻塞或非阻塞等待
  2. SIGCHLD
    子进程结束后,会向父进程发送一个信号,父进程可以对于该信号进行自定义设置方案,即可处理
  signal(SIGCHLD, SIG_IGN);

父进程忽略 SIGCHLD
内核就会自动回收子进程,不再留僵尸
父进程不用写任何 wait /waitpid

孤儿进程

产生原因
子进程未终止,父进程先终止了
处理
这是无害的,bash(init 进程(pid=1))会领养孤儿进程,由bash进程进行回收资源

守护进程

  1. 后台运行
    不接受任何终端的输入,关掉终端也不退出
  2. 脱离控制
    自己创建一个会话,成为会话组长进行管理自己
  3. 独立生命周期
    用孤儿进程的方式由bash领养,避免父进程影响
  4. 总结
    连续2次fork进行变成孤儿进程,再自己创建一个会话管理自己
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>

void daemonize() {
    // 1. 第一次 fork,父进程退出
    if (fork() != 0)
        exit(0);

    // 2. 子进程创建新会话,脱离终端
    setsid();

    // 3. 第二次 fork,防止重新打开终端
    if (fork() != 0)
        exit(0);

    // 4. 忽略子进程退出信号,避免僵尸
    signal(SIGCHLD, SIG_IGN);

    // 5. 重定向标准输入输出到空设备
    int fd = open("/dev/null", O_RDWR);
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
}

int main() {
    // 变成守护进程
    daemonize();

    // 后台死循环运行
    while (1) {
        sleep(1);
    }

    return 0;
}

环境变量

概念
用来指定操作系统运行环境的一些参数
指明动静态库的位置
PATH
指定命令的搜索途径
HOME
指定用户的主工作目录
SHELL
当前shell,一般是bash

相关命令

echo $PATH – 查看环境变量
export 变量名=val --新增一个环境变量
env 显示所有环境变量
unset 变量名;清除环境变量
set 显示所有变量(包括本地变量 + 环境变量)
特性

  1. main函数参数的第三个参数
 int main(int argc, char* argv[], char* envp[])
  1. 环境变量信息是以脚本配置文件形式存在的
    每次登陆从 ./bash_profile 当中读取内容创建表
  2. 本地变量不能被子进程继承,环境变量可以
    4.环境表本质上是一个字符指针数组,以NULL结尾

命令

常规命令
fork子进程让子进程执行该命令
内建命令
shell命令行的函数,直接读取环境变量
代码中获取设置环境变量
*getenv(const char name)
获取环境变量
**setenv(const char name, const char value, int overwrite)
设置 / 修改环境变量
*putenv(char string)
传入 name=value 格式字符串设置环境变量

虚拟地址空间

是什么

就是操作系统内核里的一个结构体:mm_struct

为什么

  1. 提高内存利用率
    不用一次性加载全部代码 / 数据
    按需分配、swap 换入换出
    解决内存碎片问题
  2. 增加内存访问控制与安全
    不能直接访问物理内存
    通过页表权限控制(只读、可读写、可执行)
    越界访问会触发段错误,保护内核和其他进程
  3. 保证进程独立性
    每个进程都以为自己独占内存
    进程之间地址空间完全隔离
    一个进程崩溃不会影响其他进程

怎么做

  1. 分段式内存管理
    程序由若干个逻辑分段组成,代码分段,数据分段,栈段,堆段等,不同的段有不同的属性,用分段的形式可以把段分离开
  2. 分页式内存管理
    产生连续的内存空间,把整个虚拟地址和物理内存空间划分为固定尺寸的大小,每一页是4KB
    通过页表映射:虚拟页号 → 物理页号
  3. 段页式内存管理
    先划分成逻辑意义的段,也就是分段机制
    再把每一个段划分成多个固定大小的页
    段号,段内页号,页内偏移定位信息

进程替换

  1. 是什么?
    进程替换 = 把当前进程的代码和数据全部换掉,换成另一个程序
    不创建新进程
    PID 不变
    只是换程序内容
  2. 执行成功会发生什么?
    用新程序的代码段、数据段覆盖当前进程
    原来的代码直接作废
    从新程序的 main 函数开始执行
    不会回到原来的程序!
    成功没有返回值
  3. 执行失败
    返回 -1
    继续执行原来的代码

系统调用接口

l → list:参数以列表形式给出(逐个写)
v → vector:参数以数组形式给出
p → path:自动去 PATH 里找命令
e → environment:自己传环境变量
逐个说明

  1. execl
    按列表传参
    需要写全路径
execl("/usr/bin/ls", "ls", "-l", NULL);
  1. execlp
    p = 自动找 PATH
    不用写全路径
execlp("ls", "ls", "-l", NULL);
  1. execle
    e = 自己传环境变量
execle("ls", "ls", "-l", NULL, envp);
  1. execv
    v = 用数组传参
char* argv[] = {"ls", "-l", NULL};
execv("/usr/bin/ls", argv);
  1. execvp
    v + p
char* argv[] = {"ls", "-l", NULL};
execvp("ls", argv);
  1. execvpe
    v + p + e
execvpe("ls", argv, envp);

MySQL shell

连接数据库
执行 SQL
管理数据库

mysql -uroot -p
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐