目录
前言:
正文
冯诺依曼体系结构
操作系统 (Operator System)
概念
目的
定位
如何理解“管理”
进程组织
基本概念
内核数据结构
代码和数据
查看进程
ps指令
top指令
父子进程
fork创建进程
小结:
前言:
在进入进程的学习之前先整理进程这个章节重点:
- 认识冯诺依曼系统
- 操作系统概念与定位
- 深入理解进程概念,了解PCB
- 学习进程状态,学会创建进程,掌握僵尸进程和孤儿进程,及其形成原因和危害
- 了解进程调度,Linux进程优先级,理解进程竞争性与独立性,理解并行与并发
- 理解环境变量,熟悉常见环境变量及相关指令, getenv/setenv函数
- 理解C内存空间分配规律,了解进程内存映像和应用程序区别, 认识地址空间
正文
冯诺依曼体系结构
常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系
各组成部分如下:
- 输入设备:
键盘
、鼠标
、声卡
、网卡
、摄像头
等 - 输出设备:
显示屏
、喇叭
、网卡
、打印机
等 - 存储器:
只读存储器
、随机存取存储器
- 运算器+控制器:
CPU中央处理器
注意:
输入、输出设备 称为外围设备,即 外设,而 外设 一般都会比较慢,比如磁盘网卡等;CPU中央处理 的速度是最快的,通过与存储器的配合,可以做到高效率处理数据(后面涉及到进程状态详细说);对数据进行预加载,CPU 计算时,直接向存储器要数据就行了,效率很高。
操作系统 (Operator System)
概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
目的
- 与硬件交互,管理所有的软硬件资源
- 为用户程序(应用程序)提供一个良好的执行环境
定位
普通用户无法直接与计算机中的硬件打交道,也就是说,在没有 操作系统
的情况下,我们几乎是无法使用计算机的,于是一些计算机大牛就创造出了各种好用的 操作系统因此操作系统
它是一款进行软硬件资源管理的软件
如何理解“管理”
一句话:先描述,再组织。这六字真言就像学习linux的指路明灯。
- 描述:通过
struct
结构体对各种数据进行描述 - 组织:通过
链表
等高效的数据结构对数据进行组织管理
具体逻辑接口如下图所示:
进程组织
基本概念
一般课本概念:进程
是程序的一个执行实例,是正在执行的程序(这种说法不全面)
补充概念:进程
由两边组成,分别是 内核数据结构+代码和数据
内核数据结构
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
代码和数据
数据生万物,任何一个进程都有自己的代码和数据,比如我们常见的 C语言
源文件,经过编译后生成的可执行程序中,就包含着二进制代码和其创建修改的时间、所处位置信息
注:
./可执行程序
其实就是将可执行程序加载至内存中,再执行描述+组织
查看进程
进程的信息可以通过 /proc系统文件夹查看
如:要获取pid为1的进程信息,需要查看/proc/1这个文件夹、
大多数进程信息同样可以使用top和ps这些用户级工具获取
ps指令
$ ps ajx | head -1 && ps ajx | grep 进程名 | grep -v grep
功能: 查看进程信息,其中利用管道进行了信息筛选,使得进程信息更加清晰(-v取反将查找的grep取消)
因为查看进程的指令太长了,所以我们可以结合前面学的自动化构建工具 make
,编写一个 Makefile
文件,文件内容如下所示:
myprocess:process.c
gcc -o myprocess process.c
.PHONY:clean
clean:
rm -r myprocess
.PHONY:catP
catP:
ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep
其中的 make catP
指令就是我们刚刚查看 进程
的那一大串指令
top指令
$ top
这个指令之前有介绍过,相当于Windows中的 ctrl+alt+del
调出任务管理器一样,top
指令能直接调起 Linux
中的任务管理器,显然,任务管理器中包含有进程相关信息
父子进程
进程间存在 父子关系
比如在当前 bash
分支下运行程序,那么程序的 父进程
就是当前 bash
分支
其中,PID
是当前进程的ID,PPID
就是当前进程所属 父进程
的ID
我们一样可以通过函数来查看 父进程
的ID值
fork创建进程
fork
函数是一个非常重要的函数,它能在当前进程下主动创建 子进程
,用于程序中
编写代码如下:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
/*
* 测试fork创建子进程
* 理解fork函数的返回值
* 通过if语句进行分流
* 总结:fork创建子进程成功时,给父进程返回子进程PID,给子进程返回0,
如果失败返回-1;通过两次fork可以发现当父进程执行后,才会去执行子进程,
父子进程间存在独立性,即父进程被kill后,子进程任然可以运行,父子进程间存在写时拷贝机制,
当子进程的值发生改变时,只会作用于子进程中
*/
int main()
{
pid_t ret = fork(); //获取返回值
int val = 1; //比较值
if(ret == 0)
{
//在子进程内再创建(孙)子进程
pid_t rett = fork();
if(rett > 0)
{
while(1)
{
val = 2; //写时拷贝
printf("二代进程正在执行 PID:%d PPID:%d 比较值为:%d 地址:%p\n\n", getpid(), getppid(), val, &val);
sleep(1);
}
}
else if(rett == 0)
{
while(1)
{
val = 3; //写时拷贝
printf("三代进程正在执行 PID:%d PPID:%d 比较值为:%d 地址:%p\n\n", getpid(), getppid(), val, &val);
sleep(1);
}
}
else
printf("进程创建失败\n");
}
else if(ret > 0)
{
while(1)
{
val = 1; //写时拷贝
printf("一代进程正在执行 PID:%d PPID:%d 比较值为:%d 地址:%p\n\n", getpid(), getppid(), val, &val);
sleep(1);
}
}
else
printf("进程创建失败\n");
return 0;
}
不难发现,子进程 是否出现取决于在当前进程中是否调用 fork 函数
fork函数工作原理:
fork 创建子进程时,会新建一个属于子进程 的 PCB ,然后把父进程 PCB 的大部分数据拷贝过来添加自己属性,暂时两者共享一份代码和数据。
各进程间是相互独立的,包括父子进程。这句话的含义是当我们销毁 父进程 后,它所创建的 子进程 并不会跟着被销毁,而是被 init 1号进程接管,成为一个 孤儿进程。
具体表现如下:
fork
创建子进程时还存在 写时拷贝
这种现象,即存在一个全局变量,当父进程的改变值时,不会影响子进程的值,同理子进程也不会影响父进程,再次印证 相互独立
这个现象。
父子进程相互独立的原因:
代码是只读的,两者互不影响
数据:当其中一个执行流尝试修改数据时,OS 会给当前进程触发发生写时拷贝,随着进程的推进,会进行详细介绍。
小结:
bash 命令行解释器本质上也是一个进程,可以被销毁
命令行启动的所有程序,最终都会变成进程,而该进程对应的父进程都是 bash
父进程被销毁后,子进程会变成 孤儿进程
进程间具有独立性,包括父子进程
因为 写时拷贝 机制,父进程不会影响到子进程