学习视频链接
黑马程序员-Linux系统编程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1KE411q7ee?p=76
目录
一、进程和程序
1.1 区别
程序占用磁盘空间
程序运行起来就成了进程,占用 CPU、内存等系统资源
1.2 并发
并发,在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但任一个时刻点上仍只有一个进程在运行。
例如,当下我们使用计算机时可以边听音乐边聊天边上网。若笼统的将他们均看做一个进程的话,为什么可以同时运行呢,因为并发。
1.3 单道程序设计
所有进程一个一个排对执行。若 A 阻塞,B 只能等待,即使 CPU 处于空闲状态。而在人机交互时阻塞的出现时必然的。所有这种模型在系统资源利用上及其不合理,在计算机发展历史上存在不久,大部分便被淘汰了。
1.4 多道程序设计
在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下,相互穿插的运行。多道程序设计必须有硬件基础作为保证。
时钟中断即为多道程序设计模型的理论基础。并发时, 任意进程在执行期间都不希望放弃 cpu。因此系统需要一种强制让进程让出 cpu 资源的手段。时钟中断有硬件基础作为保障,对进程而言不可抗拒。操作系统中的中断处理函数,来负责调度程序执行。
实质上,并发是宏观并行,微观串行(单核 CPU,多核 CPU 实现了真正的并行)
二、虚拟内存和物理内存映射关系
2.1 PCB简单了解
程序运行起来了,即 ./a 以后,就会在内核空间创建一个 PCB,PCB 是一个 struct 结构体,
结构体中记录的内容包括进程名、进程运行的时间有多长、进程编号是多少等
PCB 叫做进程控制块,或者称为进程描述符,用来记录进程状态的
2.2 MMU完成虚拟内存和物理内存映射
1、32 位系统,内存可用范围为 4G,这是一个虚拟的地址,因为我们实际的内存条可能小于 4G 大小,所以可以借助 MMU 把虚拟地址翻译成物理地址,这个就是 MMU 的作用。
一个页的大小是 4K,这个页的大小就和 MMU 大小相对应,同时寄存器的大小是 4 字节(64位系统是 8 字节)
2、两个进程中假如出现同一个地址同一个名字的变量,则 MMU 会把他们映射到不同的物理地址
3、在虚拟地址中定义一个特别大的数组(连续存储),在物理地址上是离散的
4、PCB 映射到同一块内存区域
在一块内存区域里面可以存储多个结构体,
因此两个进程之间的通信可以通过在共享区域写数据
5、不管是用户空间或者内核空间都映射到内存条,那么在内存条为什么有区分呢?
这是因为 MMU 在给 CPU 传指令的时候,会把内存分级,Windows四级、Linux两级
如果内存条中数据映射到虚拟内存中对应的是用户区域则设置为 3 级,如果是映射到内核区则设置为 0 级。用户区到内核区慢,慢在权级切换,给用户区的这块内存的权级设置为 0 级,再把权级从 0 级切换为 3。
三、PCB进程控制块
1、我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux 内核的进程控制块是 task_struct 结构体。
2、/usr/src/linux-headers-3.16.0-30/include/linux/sched.h 文件中可以查看 struct task_struct结构体定义。
使用 / 然后输入要查找的内容,定位到结构体定义
3、其内部成员有很多,我们重点掌握以下部分即可:
(1) 进程 id。系统中每个进程有唯一的 id, 在 C 语言中用 pid_t 类型表示,其实就是一个非负整数。
(2) 进程的状态,有就绪、运行、挂起、停止等状态(详细内容学习操作系统)
(3) 进程切换时需要保存和恢复的一些 CPU 寄存器
时间片用完以后会把寄存器里面的数据保存进 PCB 进程控制块,这个进程下次执行的时候,会还原寄存器中的数据
(4) 描述虚拟地址空间的信息
MMU 负责写一张表,写的这张表会存放在进程控制块中
(5) 描述控制终端的信息
用来描述进程需不需要使用终端与用户交互
(7) 当前工作目录 (Current Working Directory)
因为 shell 进程所在的目录不同,所以 ls 显示的内容不同
(8) umask 掩码
关掉终端,重新打开终端,umask 显示原来的数字
因为新打开的终端和原来的是两个进程,所以他们有不同的 PCB,刚刚改的是原来那个终端进程的 PCB
(9) 文件描述符表,包含很多指向 file 结构体的指针
(10) 和信号相关的信息
(11) 用户 id 和组 id
四、环境变量
4.1 终端命令解析原理
我们在终端中输入一个命令,这个命令会被 shell 解析器解析,解析完成后到指定目录调一个同名的可执行文件,指定目录就是所有环境变量的目录
4.2 查看环境变量
1、常见的环境变量
按照惯例,环境变量字符串都是 name = value 这样的形式,大多数 name 由大写字母加下划线组成,一般把 name 的部分叫做环境变量,value 的部分则是环境变量的值。环境变量定义了进程的运行环境,一些比较重要的环境变量的含义如下:
(1) PATH
可执行文件的搜索路径。Is 命令也是一个程序,执行它不需要提供完整的路径名 /bin/Is,然而通常我们执行当前目录下的程序 a.out 却需要提供完整的路径名 ./a.out,这是因为 PATH 环境变量的值里面包含了 Is 命令所在的目录 /bin,却不包含 a.out 所在的目录。PATH 环境变量的值可以包含多个目录,用 : 号隔开
(2) SHELL
当前 Shell,它的值通常是 /bin/bash
(3) TERM
当前终端类型,在图形界面终端下它的值通常是 xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行
(4) LANG
语言和 locale,决定了字符编码以及时间、货币等信息的显示格式
(5) HOME
当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置
2、案例
3、查看所有环境变量
五、fork 函数
5.1 fork 函数
1、查看 fork 函数
man 2 fork
2、作用
创建一个子进程(只能用这个函数创建子进程)
3、pid_t fork(void);
失败返回 -1
成功返回:① 父进程返回子进程的 ID(非负);② 子进程返回 0
pid_t 类型表示进程 ID,但为了表示 -1,它是有符号整型。(0 不是有效进程 ID,init 最小为1)
注意返回值,不是 fork 函数能返回两个值,而是 fork 后,fork 函数变为两个,父子需 [各自] 返回一个
4、测试
5.2 获取自己的进程id和父进程 id
根据上面看到的 C程序的父进程是 2225,我们查找一下这个进程是 bash
操作系统帮我们调用 fork 函数创建的进程,我们运行的程序是 bash 的子进程
六、循环创建 n 个子进程
6.1 简单循环
很显然创建的进程数量大于 5 个,这是为什么呢?
因为 i = 0 时,1 个进程变成 2 个进程
i = 1 时, 2 个进程变成 4 个进程
i = 2 时,4 个进程变成 8 个进程
...
6.2 改进循环
1、改进1
此时只生成了 5 个进程,还有一个时父进程
2、改进2
此时进程不按照生成的顺序执行 printf,甚至 bash 先争抢到了 CPU ,把终端命令提示符打印出来了
2、改进3
设置睡眠时间的长短,因为程序执行时间小于 1 秒,所以进程就按照顺序执行出来了
6.3 进程对数组进行排序输出
代码
编译执行
运行结果
七、进程共享
7.1 父子进程 fork 后的异同
刚 fork 之后
父子相同处:全局变量、.data、.text、栈、堆、环境变量、用户 ID、宿主目录、进程工作目录、信号处理方式 ...
父子不同处:1、进程 ID 2、fork 返回值 3、父进程 ID 4、进程运行时间 5、闹钟(定时器) 6、未决信号集
似乎,子进程复制了父进程 0-3G 用户空间内容,以及父进程的 PCB,但 pid 不同。真的每 fork 一个子进程都要将父进程的 0-3G 地址空间完全拷贝一份,然后在映射至物理内存吗?
当然不是! 父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
7.2 测试
八、父子进程 GDB 调试
使用 gdb 调试的时候,gdb 只能跟踪一个进程。可以在 fork 函数调用之前,通过指令设置 gdb 调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。
set follow-fork-mode child 命令设置 gdb 在 fork 之后跟踪子进程。
set follow-fork-mode parent 设置跟踪父进程。
注意,一定要在fork函数调用之前设置才有效。