进程概念
冯诺依曼
核心
所有硬件都是围绕内存工作的,所有数据必须先加载到内存,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
共享同一个虚拟地址空间
创建一个进程的流程
- 找到父进程的PCB对象,malloc一个PCB来存放子进程,用父进程的PCB初始化子进程,子进程的PCB指向父进程的代码和数据,子进程和父进程都加入调度队列开始排队等待调度
- 当有一方要改变数据时,就会触发写时拷贝
进程终止
终止的几种情况
正常终止,结果正确 :代码正常执行完毕,正确退出码是0
正常终止,结果不正确 :代码也正常执行结束了,但是不在合适的地点退出,退出码不为0
异常 :代码没有正常执行结束,可以使用**echo $?**来查看
exit code:当进程收到信号而退出后,可以父进程可以通过回收资源收到退出码,以查看子进程的退出情况
终止的操作对比
exit和_exit :
_exit是系统调用,
exit:_exit+刷新缓冲区+关闭文件
main 的 return
return n 等价于 exit (n)
其他return :进程还在跑
进程终止系统的行为
- 根据被终止进程的标识,操作系统会从它的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分区中 ,当进程被调度的时候再把数据加载进来
转换流程

僵尸进程
产生原因
子进程终止,父进程不回收子进程资源
危害
子进程的数据没有被回收,就会内存泄漏
避免
-
进程等待
主动调用接口,waitpid进行阻塞或非阻塞等待 -
SIGCHLD
子进程结束后,会向父进程发送一个信号,父进程可以对于该信号进行自定义设置方案,即可处理signal(SIGCHLD, SIG_IGN);
父进程忽略 SIGCHLD
内核就会自动回收子进程,不再留僵尸
父进程不用写任何 wait /waitpid
孤儿进程
产生原因
子进程未终止,父进程先终止了
处理
这是无害的,bash(init 进程(pid=1))会领养孤儿进程,由bash进程进行回收资源
守护进程
-
后台运行
不接受任何终端的输入,关掉终端也不退出 -
脱离控制
自己创建一个会话,成为会话组长进行管理自己 -
独立生命周期
用孤儿进程的方式由bash领养,避免父进程影响 -
总结
连续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 显示所有变量(包括本地变量 + 环境变量)
特性
-
main函数参数的第三个参数
int main(int argc, char* argv[], char* envp[])
-
环境变量信息是以脚本配置文件形式存在的
每次登陆从 ./bash_profile 当中读取内容创建表 -
本地变量不能被子进程继承,环境变量可以
4.环境表本质上是一个字符指针数组,以NULL结尾
命令
常规命令
fork子进程让子进程执行该命令
内建命令
shell命令行的函数,直接读取环境变量
代码中获取设置环境变量
*getenv(const char name)
获取环境变量
**setenv(const char name, const char value, int overwrite)
设置 / 修改环境变量
*putenv(char string)
传入 name=value 格式字符串设置环境变量
虚拟地址空间
是什么
就是操作系统内核里的一个结构体:mm_struct
为什么
- 提高内存利用率
不用一次性加载全部代码 / 数据
按需分配、swap 换入换出
解决内存碎片问题 - 增加内存访问控制与安全
不能直接访问物理内存
通过页表权限控制(只读、可读写、可执行)
越界访问会触发段错误,保护内核和其他进程 - 保证进程独立性
每个进程都以为自己独占内存
进程之间地址空间完全隔离
一个进程崩溃不会影响其他进程
怎么做
- 分段式内存管理
程序由若干个逻辑分段组成,代码分段,数据分段,栈段,堆段等,不同的段有不同的属性,用分段的形式可以把段分离开 - 分页式内存管理
产生连续的内存空间,把整个虚拟地址和物理内存空间划分为固定尺寸的大小,每一页是4KB
通过页表映射:虚拟页号 → 物理页号 - 段页式内存管理
先划分成逻辑意义的段,也就是分段机制
再把每一个段划分成多个固定大小的页
由段号,段内页号,页内偏移定位信息
进程替换
- 是什么?
进程替换 = 把当前进程的代码和数据全部换掉,换成另一个程序
不创建新进程
PID 不变
只是换程序内容 - 执行成功会发生什么?
用新程序的代码段、数据段覆盖当前进程
原来的代码直接作废
从新程序的 main 函数开始执行
不会回到原来的程序!
成功没有返回值 - 执行失败
返回 -1
继续执行原来的代码
系统调用接口
l → list:参数以列表形式给出(逐个写)
v → vector:参数以数组形式给出
p → path:自动去 PATH 里找命令
e → environment:自己传环境变量
逐个说明
-
execl
按列表传参
需要写全路径execl("/usr/bin/ls", "ls", "-l", NULL);
-
execlp
p = 自动找 PATH
不用写全路径execlp("ls", "ls", "-l", NULL);
-
execle
e = 自己传环境变量execle("ls", "ls", "-l", NULL, envp);
-
execv
v = 用数组传参char* argv[] = {"ls", "-l", NULL};
execv("/usr/bin/ls", argv); -
execvp
v + pchar* argv[] = {"ls", "-l", NULL};
execvp("ls", argv); -
execvpe
v + p + eexecvpe("ls", argv, envp);
MySQL shell
连接数据库
执行 SQL
管理数据库
mysql -uroot -p