目录
1.基本概念
课本概念
:程序的一个执行实例,正在执行的程序等内核观点
:担当分配系统资源(CPU时间,内存)的实体。
2.初步理解
以前我们的任何启动并允许程序的行为,都是由操作系统帮助我们将程序转换成为进程,来完成特定的任务。(在Linux中,./运行一个程序。在windows中,鼠标双击运行一个程序。他们都将一个程序转换成了进程)
如图,我们将磁盘中的程序加载到了内存当中,形成了代码和数据。
但这并不算一个进程,操作系统为了管理这些加载到内存当中的代码和数据,需要先描述再组织
,在内核当中为这些代码和数据创建一个个的数据结构对象。(操作系统书中叫PCB,Linux操作系统下是task_struct)
例如:
- 我们想要释放进程A,操作系统就在所有PCB中寻找关于进程A的,然后free释放掉对应的代码和数据以及PCB。
- 我们想要运行进程中优先级最高的,操作系统就遍历所有的PCB找到对应的进程,将其加入CPU中运行。
- 我们想要再运行一个程序,操作系统将磁盘中的程序加载到内存中转化为代码和数据。并且又为其创建一个PCB,将进程的属性加入PCB中,并链入原先的PCB链表当中。
因此:
对进程的管理转化为对PCB链表进行增删查改。
进程 = 加载到内存中的代码和数据 + 内核关于进程的相关数据结构
3.描述进程-PCB
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
3.1task_struct-PCB的一种
- 在Linux中描述进程的结构体叫做task_struct。
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
3.2task_ struct内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
4.组织进程
可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct双链表的形式存在内核里。
5.查看进程
5.1通过ps指令查看
- 单独使用ps命令,会显示所有进程信息。
ps -axj
通过对grep指令的搭配,可以查找想要查看的进程信息
ps -axj | head -1 && ps -axj |grep myprocess | grep -v grep
- 因为grep也是进程,因此加入grep -v grep可以过滤掉grep进程信息。
5.2通过系统目录查看
进程的信息可以通过 /proc 系统文件夹查看
其中有很多文件夹都是以数字命名,这些数字其实就是对应进程的PID,对应文件夹当中记录着对应进程的各种信息。
当我们创建一个进程后,就会在/proc目录下创建对应的文件夹保存进程的信息。一旦我们关闭掉这个进程,/proc目录下相应的进程文件夹就会被删除。
6.通过系统调用获取进程的PID和PPID
通过使用系统调用函数getpid和getppid
即可分别获取进程的PID和PPID。
使用下列代码测试。
运行结果,通过getpid获取的pid值与ps命令查询的值是相同的。
其次,通过不断启动终止进程,我们发现每次启动进程的pid都会改变,但是ppid并不会改变,这个ppid又是什么呢?
21615是我们的bash(命令行解释器)。
我们可以得到一些结论:
bash命令行解释器,本质上它也是一个进程!
命令行解释器启动的所有程序,最终都会变成进程,而该进程对应的父进程都是bash
7.通过系统调用创建进程-fork初识
//创建子进程
//有两个返回值,创建成功给父进程返回子进程PID,给子进程返回0
//创建失败返回-1
#include<unistd.h>
int fork(void)
fork 函数能在当前进程下主动创建 子进程 ,用于代码程序中。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
printf("AAAAAAAAAAAA\n");
int ret = fork();
printf("BBBBBBBBBBBB,PID: %d \n",getpid());
sleep(1);
return 0;
}
运行结果:
BBBBBBBB被打印了两次,可见fork系统调用之后程序就变成了两个执行流,即一个父进程一个子进程。
但通常我们可以根据fork的不同返回值,搭配if完成分流,使父子进程执行不同的代码。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0)
{
perror("fork");
return 1;
}
else if(ret == 0)
{
//child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else
{
//father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
运行结果:
其中,fork之后的两个执行流,谁先执行由调度器决定
fork函数工作原理:
- fork 创建子进程时,会新建一个属于
子进程的PCB
,然后把父进程 PCB 的大部分数据拷贝进子进程的PCB中
,即子进程和父进程的PCB中的数据绝大部分是相同的。两个进程的PCB指向同一份代码和数据即两者共享一份代码和数据
我们知道进程是具有独立性的,父子进程同样具有独立性。
但父子进程两者共享同一份代码和数据,他们的独立性从何谈起?
- 从代码层面:代码是只读的,父子进程只能读取代码,只不过可能读取的部分不同,相互不影响
- 从数据层面:当其中一个执行流尝试修改数据时,OS 会给当前进程触发
写时拷贝
机制
如何理解fork函数有两个返回值?
当return时,函数的主体功能已经完成,此时子进程已经被创建,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。