什么是进程?
操作系统中, 进程可以同时存在非常多的。根据我们之前谈的操作系统具有“管理”的特性, 那么就有,既然要管理,就要 --- 先描述,在组织!!!
由冯诺依曼体系结构我们知道磁盘中的文件都是要加载到内存然后由内存与CPU交互完成一些任务的。
所以其实当我们运行一些程序时就会为我们创建一个进程。
这个进程是什么样的?
那么当我们运行我们写出来的c/c++代码时。
首先从磁盘中把我们的代码和数据加载到内存中
一个进程就要有一个PCB(process control block)
这个PCB要在内存中加载的操作系统中malloc出对应的空间,实质上就是一个结构体类型,
里面包含了各种进程有关的属性,并且有一个指针可以指向自己的代码和数据!!!
那么我们得出一个结论:进程 = PCB + 程序的代码和数据
在后面我们就可以把内存中加载的操作系统内部进行各种PCB的管理工作,也就是说
我们对进程的管理就转变成了我们对链表(各种数据结构,不一定就是链表)的增删查改!!!
具体到Linux中的进程是什么样的?
我们前面知道了:进程 = PCB + 自己的代码和数据
这里的PCB是一个统称,因为我们将来要接触各种各样的操作系统, 他们的PCB实现当然是不同的,名字可能也有差异。
那具体到Linux中是怎么样的呢?
struct task_struct
{
//Linux控制块
}
这里的task_struct就是具体的称呼!!!
下面就理解一个概念:如何理解进程动态运行?
只要我们的进程task_struct将来在不同的队列中,进程就可以访问不同的资源!!!
故有:调度运行进程,本质就是让进程块task_struct进行排队!!!
进程task_struct内部的属性有哪些?
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
这些是task_struct内部的一些属性名称,我们后续都将一一学习。
如何启动一个进程?
a.
我们在Linux中的./xxx本质就是让系统启动进程并运行 === 我们写的代码形成可执行 ===
系统命令 === 可执行文件。
所以:在Linux中运行中的大部分执行操作,本质都是运行进程!!!
我们在Linux中也可以查看进程
通过ls /proc命令
进程的标识符
每个进程都有自己的唯一标识符,叫做进程pid
一个进程,想知道自己的pid???
如何做呢?
那么问题来了,我们能直接访问操作系统来查询自己的pid吗?答案是不能的
我们已经讲过用户想要访问操作系统的内容就必须要通过操作系统提供的系统调用接口来获取内部信息!!!
那么如何获取我们进程的pid呢?
就使用getpid这个接口即可获取到我们需要的进程pid信息!!!
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid = getpid();
printf("pid:%d\n", pid);
sleep(1);
return 0;
}
我们来创建一个一直在跑的进程,方便我们查看进程
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4
5 int main()
6 {
7 pid_t pid = getpid();
8
9 while(1)
10 {
11 printf("pid:%d\n", pid);
12 sleep(1);
13 }
14
15 return 0;
16 }
这是我们的进程在运行,我们看到pid就是属性栏的第二列就是我们该进程自己的pid。
显然ctrl + c就是在用户层面终止程序,
kill + -9 + pid 可以用来直接杀掉进程
进程创建的代码方式(重操作,轻原理 --- 后续会再次详谈进程创建)
getpid():获取当前进程的pid
getppid():获取当前进程的父进程pid
在Linux中所有的进程又有一个统一的父进程,就是bash --- 命令行解释器
比较方便查看进程的一个xshell脚本:while :; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep ; sleep 1; done
而我们接下来要学习一个新的接口,他能够为我们创建子进程
fork();
fork之后父子代码共享!!!
创建一个进程本质就是系统多了一个进程
多了个进程就是多个
1.内核task_struct
2.有自己的代码和数据
父进程的代码和数据是由磁盘加载而来的。
那么子进程的代码和数据呢???
默认情况下继承父进程的代码和数据?
我们为什么要创建子进程,我们是要子进程执行和父进程不一样的代码
#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两个问题
1.同一个id,怎么可能即是0又是一个大于0的数
2.fork会有两个返回值,返回两次
对于第一个问题现在我们还不能很好的解释后续再谈
第二个问题
我们知道
进程 = 内核数据结构task_struct + 代码和数据
进程一定要独立,进程具有独立性
所以父子各自独立,原则上数据要分开
且代码是只读的
子进程会继承父进程的代码,又代码是只读的,所以fork函数内部必定是要先去创建子进程的
然后进行返回id,但我们知道子进程是完全继承父进程的代码的,故会返回两次值。
我们在后续可以一次性创建多个进程
进程的PCB中会记录自己对应的可执行程序的路径 -》 exe -> 路径
也会记录该进程当前的工作路径 -》 cwd -> 路径
chdir("");这个函数可以改变我们的进程工作路径!!!
结论:每个进程在启动时都会记录自己在当下哪个路径启动,进程的当前路径!!!