[Linux] 初识进程(上)
旧识铺垫
我们之前学过冯 · 诺依曼奠定了现代计算机的硬件体系,目前现代计算机的五大硬件单元:
- 运算器
- 控制器
- 存储器:内存等
- 输入设备:键盘等
- 输出设备:显示器等
所以这就引出了我们的一句概念:硬件结构决定软件行为。
比如运行某一程序,因为程序代码是保存在外部存储器中的,而使他运行操作系统就要从外存读入代码到内存中,再在内存中创建进程,给它分配运行必须的资源,然后插入就绪队列中。这就是一个硬件结构决定软件行为的典例。
读取速度由中央处理器CPU
决定。
再细化为CPU
的主频:时钟振荡周期,一秒钟可以处理多少个指令,值越大,系统吞吐量越大。
操作系统
那么操作系统是什么?
- 其实质是一个软件。
- 目的:让计算机更好用
- 功能:统筹管理计算机上的软硬件资源
- 如何管理:先描述,再组织。
操作系统要对硬件进行管理,首先要知道他们的描述信息。操作系统不亲自收集这些信息,而是驱动程序(中间执行者)来操作,操作系统通过驱动程序管理硬件信息。
lib
:用户不能直接接触操作系统,为了把风险降到最低,操作系统提供了一套系统调用接口供外部用户使用,完成某些功能,但是这些接口不太好用。SHELL
:为了更加好用,所以就封装成了一些shell,用户通过命令就可以操作操作系统。还有lib,用户使用库函数调用。
调用情况如下:
库函数(lib
)和系统调用接口的关系:上下级的调用关系,库函数就是对系统调用接口的一层封装。
操作系统:对下管理软硬件资源,对上提供良好的执行环境。
操作系统的定位就是:搞管理的软件。
进程概念
- 进程:进行(运行)中的程序。
- 程序:位于硬盘的一堆代码/指令集。
操作系统通过PCB
来管理运行中的程序(进程)。
-
PCB
:进程控制块(process control block
) ,Linux
中实质是一个结构体,名为task_struct
。其中包含了很多描述信息:时间片:cpu在一个进程上运行的时间 进程标示符: 描述本进程的唯一标示符,用来区别其他进程。 状态: 任务状态,退出代码,退出信号等。 优先级: 相对于其他进程的优先级。 程序计数器: 程序中即将被执行的下一条指令的地址。(存储即将处理的指令) 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。 I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。某些进程在CPU上运行了多长时间。。
磁盘中每一个程序执行时都会加载到内存中,就会产生此进程的PCB
,其中包含的文件指针指向此时内存中的进程(及上下文数据等),CPU
通过PCB
去调度进程。
【注】CPU的分时机制:轮询调度进程
所以进程是什么要从两个角度来看:
- 用户角度:进程就是运行中的程序。
- 操作系统角度:操作系统运行一个程序,需要去描述这个程序的运行过程,而这个描述是通过一个结构来描述
task_struct
,统称PCB
,因此对OS
来说进程就是PCB
。
进程查看
-
/proc
进程运行信息存放目录 -
ps -elf/aux
查看系统上进程信息 (更加详细)
(注:ps -aux
与ps aux
是不同的) -
在程序中使用接口
getpid()
:获取调用进程的进程ID
例如:printf("pid = [%d]",gitpid());
tty
查看终端号
进程创建
fork()
使用这个函数需要包含头文件<unistd.h>
。
函数原型:
pid_t fork(void);
fork()
通过复制调用进程,创建一个新的进程(子进程),子进程复制的就是父进程的PCB
,但程序标识符pid
不同。
父子进程数据,代码看起来都一样 ,但代码共享,数据独有。这涉及写时拷贝技术,我们在下篇中详细介绍。
创建子进程的意义:
- 压力分摊
- 干其他工作
创建流程:
进程调用fork
,当控制转移到内核中的fork
代码后,内核进行操作:
- 分配新的内存块和内核数据结构给子进程。
- 将父进程部分数据结构内容拷贝至子进程 (给子进程创建PCB,然后拷贝父进程数据进入)
- 添加子进程到系统进程列表汇总。
-
fork
返回,开始调度器调度。
getpid()
函数对于父进程中返回子进程的pid
,对于子进程返回 0
,失败返回 -1
。
所以父子进程的区分标准就是返回值,由此引出下面代码:
代码实例
int main(){
printf("parent pid:%d\n",getpid()); //通过getpid接口获取调用进程的pid
pid_t pid = fork();
if(pid < 0){ //创建失败
return -1;
}
else if(pid == 0){
printf("child子进程\n");
}
else{
printf("parent父进程\n");
}
}
vfork()
创建子进程,并阻塞父进程。
-
fork
创建pcb
并开辟内存空间,vfork
创建的子进程有自己的PCB
,但和父进程共用同一块虚拟空间。 - 子进程先运行,父进程等到子进程
exit
退出或者程序替换后,父进程才运行。 - 否则同时运行的话,因为父子进程共用虚拟地址空间,会造成调用栈混乱,因此需要阻塞父进程。
(比如父进程调用了memcpy
函数) -
vfork
子进程如果调用return
退出,会释放资源,会导致父进程陷入混乱,或者错误。 - 子进程不终止,父进程一直处于阻塞状态。
fork/vfork 与 clone
vfork
函数相对于早期fork
函数:略过开辟空间、拷贝数据的过程,现在fork
函数的写时拷贝技术也避免了无用的拷贝,所以vfork
接口使用形式式微。
比较:fork/vfork
都是库函数接口,clone
是系统调用接口。
- fork/vfork都是调用的是clone函数。
【注】换行符
\n
的作用:
- 换行
刷新缓冲区
在本篇博客的结尾给出进程初识
阶段(下篇)博客:
【 https://blog.****.net/qq_42351880/article/details/89043560 】
有兴趣的看官大佬可以继续了解一下~