本文已参与「新人创作礼」活动,一起开启掘金创作之路。
旧识铺垫
我们之前学过冯 · 诺依曼奠定了现代计算机的硬件体系,目前现代计算机的五大硬件单元:
- 运算器
- 控制器
- 存储器:内存等
- 输入设备:键盘等
- 输出设备:显示器等
所以这就引出了我们的一句概念:==硬件结构决定软件行为==。
比如运行某一程序,因为程序代码是保存在外部存储器中的,而使他运行操作系统就要从外存读入代码到内存中,再在内存中创建进程,给它分配运行必须的资源,然后插入就绪队列中。这就是一个硬件结构决定软件行为的典例。
读取速度由中央处理器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的作用:
- 换行
刷新缓冲区