本文就来简单地讲述一下进程的相关概念,以及fork函数的使用。
进程的概念
我们之前任何启动并运行程序的的行为,由操作系统转换为进程,从而完成特定的任务。
在磁盘上写好的代码(即为可执行程序),本质上就是一个普通的二进制文件。然后如果要将文件运行起来,就需要将文件的代码与数据加载到内存之中。随着磁盘上加载到内存中的程序越来越、多,操作系统就需要对这些数量很多的代码和数据进行管理。对此操作系统就需要对这些进行先描述,再组织的方式,操作系统就会在内核中为每个加载带内存的进程创建一个数据结构对象,被称为pcb/task_struct,它提取了所有进程的属性。之后我们对于进程的管理就可以简化对数据结构进行对应的控制操作,在寻找到我们所需要的进程之后,就可以通过这个结构体寻找到其储存在内存之中的代码与数据。
总结一下:进程 = 内核关于进程的相关数据结构 + 当前进程的代码与数据
描述进程-PCB
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block), Linux操作系统下的PCB是: task_struct。
task_struct-PCB的一种
- 在Linux中描述进程的结构体叫做task_struct。
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
task_struct内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
查看进程
我们在这个编写一个简单地死循环程序,然后对进程进行观察。
ps指令进行查看
ps axj 指令
使用该指令可以查看当前正在运行的进程
使用 | 可以将进程显示的第一行过滤出来得到对应的信息(ps axj | head 1)
再结合grep相关的指令得到与之前运行的myprocess代码相关的进程
(ps axj | head -1 && ps axj | grep myprocess)
可以看到其中的第一行就是运行的程序,第二行就是在运行grep指令的时候其本身就是一个进程。
如果不希望看到grep指令的进程,可以使用(| grep -v grep)来实现过滤。
将同一个可执行程序多次执行就会产生多个进程
系统文件进行查看
除了使用指令的方式进行进程的查看,我们还可以通过系统文件的方式进行查看。
可以看到我们执行的两个进程的PID号,再进入进程号的文件目录可以看到进程运行的信息
当我们将正在进行的进程杀死,再次选择进入该文件夹就会爆出警告
通过系统调用获取进程标示符
- 进程id(PID)
- 父进程id(PPID)
使用man手册可以参看该函数getpid
我们使用getpid函数来得到进程号,再与通过指令得到的进程号进行比对,可以得出这两者是相等的。
反复多次的打开编写的可执行程序,可以发现PID一直在变化,而父进程PPID一直不变
通过指令进行搜索查看其父进程,可以看到父进程是bash即命令行解释器bash命令行解释器,本质上也是一个进程;命令行启动的所有程序,着重都会变成进程,而该进程对应的父进程都是bash。
我们可以使用kill -9 [进程号] 来杀掉一个进程
通过系统调用创建进程-fork
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("AAAAAAAAAAAAAAAAAAAAAAA\n");
fork();
printf("BBBBBBBBBBBBBBBBBBBBBBB\n");
sleep(1);
return 0;
}
运行上述的代码可以得到下图中的结果,对其分别获取进程号与父进程号。
可以看到创建出来的进程是父子关系。在执行A的时候是一个执行流,在执行B的时候就变为了两个执行流。
fork的返回值
在手册之中我们可以看到与返回值有关的信息(父子进程谁先跑由操作系统决定)
父进程返回子进程的pid,子进程返回0。
得到如下的结果:
fork之后通常使用if进行分流![](https://i-blog.csdnimg.cn/blog_migrate/822e44cfd9d6e5a43581166543aeaa25.png)
fork之后执行流会变为两个执行流;父子进程谁先运行由调度器决定;fork之后的代码共享通常由if进行分流。
原理:
fork做了什么?
fork如何看待代码和数据
进程在运行时,是具有独立性的!
父子进程在运行时也是独立的,父子进程的PCB不同,但是指向的是同一块代码与数据独立性的保证由一下两个方面:
- 代码:代码是只读的,
- 数据:当有一个执行流尝试修改数据是时,操作系统会自动给我们当前的进程触发一个功能:写时拷贝
fork理解两个返回值
当函数内部准备执行return时,我们的主体功能已经完成,在准备return前,子进程已经创建完毕,return父子进程都需要执行该语句,返回时就是想ret变量写入的过程,因此会发生写时拷贝。
最后将讲一个vim批量化注释的方法:
批量化注释:Ctrl + v + 选中需要注释的行 + 大写i +// + esc
批量化删除:Ctrl + v + 使用hjkl选中区域 + d 删除