目录
一、进程概念:
描述:
组织:
二、Linux中的进程管理:
指令:ps ajx
三、父子进程:
PID和PPID的调用查看:
四、创建子进程------fork:
一、进程概念:
首先,要理解进程,就要了解他的属性,就是要对进程先进行描述在进行组织,
进程可初步理解为:将磁盘中的程序加载到内存中,这个在内存中正在运行的程序叫做进程,也可以叫做任务。在冯诺依曼体系中,我们了解到操作系统进行软硬件资源管理的时候是先描述在组织的。
描述:
任何一个程序,在加载到内存中形成进程的时候,操作系统会先创建一个对这个进程进行描述的结构体对象,里面有描述这个进程的属性,这个结构体类型就叫做PCB(process ctrl block,进程控制块),PCB是这个进程的属性结合,例如下:
然后操作系统就根据进程的PCB类型,为该进程实例化出对应的PCB对象,然后将这个实例化的对象和自己所写的代码和数据放到内存中,这个对象和代码数据加起来就是一个进程。
如上下图:
在系统中不仅仅只有一个进程,比如说我在电脑中,边听歌变打游戏,并且QQ也是挂着的,那么在内存中就会存在多个进程,如下所示:
所以操作系统对进程做管理本质是对PCB做管理而不是对代码和数据,那么操作系统怎么是如何通过PCB找到对应的代码和数据呢 ------ 在PCB中会存放相关的指针信息,可以通过指针找到自己的代码和数据。
那么进程在内存中既然已经被描述起来了,那么就需要对这些进程进行组织
组织:
在系统中会存在多个进程,这些进程都是通过PCB进行描述出来,在PCB中加上一个指针(struct PCB* next),这个指针就是指向当前进程的下一个进程的,将所有的进程通过单链表链接起来,这样操作系统对进程的管理就变成了对链表的增删查改,这样就组织起来了
二、Linux中的进程管理:
PCB实际上是对进程控制块的统称,在Linux中就有个具体的名字叫做-------task_struct,所以PCB和task_struct之间的关系就是:task_struct是PCB的一种。task_struct是Linux内核中的一种数据类型,根据task_struc实例化对应的PCB对象,这个结构体中的属性非常多,如下:
标示符: 描述本进程的唯一标示符,用来区别其他进程
状态: 任务状态,退出代码,退出信号等
优先级: 相对于其他进程的优先级
程序计数器: 程序中即将被执行的下一条指令的地址
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
其他信息
指令:ps ajx
ps axj就能查看当前机器下所有的进程,可以用管道,在后面进行更多的修饰,比如与grep搭配着用可以找到目标的进程。
如下写一个进程加载到内存中:
int main()
{
while(1)
{
cout<<"一个进程正在运行"<<endl;
sleep(1);
}
return 0;
}
然后对其进行编译生成可执行程序:mytest,然后进行 ./mytest 运行,./mytest实际上就是将可执行程序从磁盘加载到内存中进行管理的。
在进程开始运行的时候,接着使用指令查看进程信息,如下:
PID:是操作系统中用于标识每个进程的唯一标识符
PPID:是操作系统中用于标识每个进程的父进程的唯一标识符
COMMAND:指的是在Linux系统中,进程启动时使用的命令或程序
也可以在/proc目录下查看:proc目录中,包含的系统当中 动态运行的 所有进程的相关信息,这些进程都是在内存中的,是内存级的,这里是将这些进程可视化了
然后如果将进程关闭就在/proc目录下就找不到了,并且用ps也查不到了:
然后我们在将进程运行起来,就又可以找到了,但是重新生成的进程和原来的进程一般PID不一样
然后我们将这个进程的信息通过 -l 来进行详细打印:
其实下面还有很多属性,但是我们现在只看两个:cwd和exe
exe:在进程信息中,exe表示的是可执行文件,EXE是“executable file”的缩写
cwd:在进程信息中,cwd表示当前进程的工作目录,进程运行时的所在目录就是该进程的当前目录,比如说touch命令在执行的时候首先它自己要先变为进程,然后在它的当前目录下创建一个文件,这个当前目录就是这个进程运行时的所在目录,进程的工作目录就是进程启动的时候所在的当前目录,这样在程序中加上如下fopen语句,但是没有给路径,但是系统会默认将cwd的路径放在创建文件的前面,这样的话就可以找到创建的路径了
然后进行运行之后,尽管我没有交代在哪儿创建,到那时cwd会将它的目录放到log.txt之前,这样就可以找到在哪儿进行创建了。
三、父子进程:
首先要知道什么是bash:
一个窗口(命令行解释器)就是一个bash,bash是在用户目录下的一个进程,当执行一个进程的时候,本质是bash进程创建了一个子进程去执行的。
存在父子进程的概念,一个进程的默认父进程是bash进程,当前进程的标识符ID可以和当前进程的父进程ID可以用getpid()和getppid()进行查看
PID和PPID的调用查看:
可以在系统调用中使用getpid()来看当前进程的PID,使用getppid()看当前进程的父进程的PID
查看这个进程的父进程,就是bash进程,在bash下面的运行的程序, 他们的父进程, 都是bash,bash命令行的PID不变, 我们不管运行多少次程序, 他们的父进程都是bash命令行的PID,
但是如果我们重启xshell,bash的PID才会变,这也可以理解为新开了一个窗口,肯定会变。
四、创建子进程------fork:
fork是在手册二中的,可以使用man查看fork函数的用法:
fork函数的作用就是在当前进程下创建一个子进程,这个子进程的父进程就是当前进程
如上,手册里面说:如果成功创建一个子进程后就会返回一个0,还会返回子进程的PID,失败就会返回-1给父进程,没有子进程。
接下来写如下了代码,使其运行起来成为一个进程:
运行结果:(会一直运行下去)
如上,其就会一直进行死循环的打印父子进程,其实际上就会从一个执行流又分了一个子执行流,一个是橙色的一直在循环中进行打印,一个是蓝色的在父进程循环中进行打印。
通过上述代码我们会有几个疑问:
1、为什么fork要给子进程返回0,给父进程返回子进程的PID
2、fork函数究竟干了什么?
3、一个函数是如何做到返回两次的,这怎么理解?
4、同一个变量怎么会有不同的内容,如何理解?
1、为什么fork要给子进程返回0,给父进程返回子进程的PID:
这样有两个返回值是为了区分子进程和父进程,我们不妨想想,为什么要有子进程和父进程呢?这样的原因主要是为了让子进程执行和父进程不一样的代码块,所以就需要不同的返回值来区分子进程和父进程。
这样就可以用if 、else if、 else进行分流操作,来完成子进程完成和父进程不一样的操作
并且一个子进程是只有一个父进程的,一个父进程可能有许多子进程,所以就需要给子进程一个返回0来标记,给父进程返回子进程的PID来管理子进程。
一般而言:fork之后的代码父子进程共享!!!
2、fork函数究竟干了什么:
当通过fork创建一个子进程之后,父子进程共享剩余的代码,在内存中的代码是不可以被修改的,通常的权限为只读,这样既即使父子进程共用剩余代码,也影响不大的,
但是数据是可以被修改的,那么就不能单纯地让子进程指向父进程的数据,那样的话一个进程崩了就会影响另一个进程的运行
于是解决方法:当子进程需要访问父进程的数据的时候,在内存别的地方开辟一块空间,将父进程中子进程待访问的数据拷贝到新的空间,然后将子进程的数据区指向新开的那一块地方,然后随便子进程进程修改。
注意:
不是将父进程的所有数据都拷贝而是只将子进程需要访问的数据拷贝,这样是为了节省空间
3、一个函数是如何做到返回两次的,这怎么理解?
4、同一个变量怎么会有不同的内容,如何理解?
首先,在任何一个平台,进程运行的时候是具有独立性的,一个进程崩了并不会影响别的进程的运行,所以像上面第二个问题中说到:父子进程,代码是共享的,但是数据不会,所以,这种子进程需要多少数据修改,就在父进程的数据中拷贝对应部分到新空间进行修改-----------这就叫做写时拷贝
这样在赋值角度来说就会有两次赋值,id就会有两个值,但是id为什么会访问两个数据这个要到后面才会了解到
那么父子进程谁先运行呢?------这个是有调度器决定的,是不确定的,调度器保证进程被公平调度,毕竟CPU一般只有一个,而进程有多个