ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux操作系统:进程(1)

Linux操作系统:进程(1) 前言本章要谈论进程的概念为后续进程状态做铺垫一、进程的基础概念1是什么进程是内核的数据结构程序的代码和数据。2为什么当我们的所有程序执行时都会变成进程被操作系统去运行操作系统需要一种方式去“记住”每一个程序的状态合理的分配资源。没有进程的话操作系统不知道谁在跑谁在消耗资源。进程是操作系统资源分配的基本单位。3怎么样操作系统中会存在多个进程所以必然要对这些进程去做管理通过上一篇文章所得出的结论也就是“先描述再管理”通过数据结构的方式将进程管理起来。进程描述PCB是在内核之中的存在一个结构体(task_struct)去描述这个进程。我们知道内存分为用户空间和内核空间我们的进程描述PCB是存在于系统内核之中的我们平时写的代码数据等之类的加载到内存就是加载到用户空间之中两者井水不犯河水。此时呢首先将位于磁盘的代码和数据加载到用户空间之中此时应该注意就是此时还不算进程然后再在操作系统中准确来说是在系统内核中形成一个描述这个的结构体(task_struct)当然这个结构体以后再说此时就可以说是一个进程了。所以总结一下无非我们要讨论与学习的就是只有两个一个就是这个结构体的内部是什么样的也就是属性还有一个就是操作系统是这么管理的也就是数据结构。所以我们先来学习一下这个属性是什么中途会穿插少量数据结构的内容。二、进程描述---PCB(1)概念进程信息被放在一个叫做进程控制块的数据结构之中可以理解为进程属性的集合。课本上称之为PCBLinux操作系统下的PCB是:task_struct也就是Linux中描述进程的结构体。task_struct是Linux内核的一种数据结构类型它会被装载到RAM(内存)里并且包含着进程的信息(2)task_struct这个是Linux操作系统下的PCB它的内容包括• 标⽰符: 描述本进程的唯⼀标⽰符⽤来区别其他进程。• 状态: 任务状态退出代码退出信号等。• 优先级: 相对于其他进程的优先级。• 程序计数器: 程序中即将被执⾏的下⼀条指令的地址。• 内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针• 上下⽂数据: 进程执⾏时处理器的寄存器中的数据[休学例⼦要加图CPU寄存器]。• I∕O状态信息: 包括显⽰的I/O请求,分配给进程的I∕O设备和被进程使⽤的⽂件列表。• 记账信息: 可能包括处理器时间总和使⽤的时钟数总和时间限制记账号等。• 其他信息三、指令或者代码1、查看进程(1)指令ls /proc/。通过这个指令就可以得到进程的进程信息。(2)指令top。指令就是top这个是动态的(停下来按ctrlc)(3)指令ps。这个指令需要vim一个文档。2、父进程和子进程(1)基本概念子进程是由父进程创建出来父子之间是以树形关系也就是说一个子进程只有一个父进程而一个父进程可以有多个子进程。PID(Process ID)进程的ID。进程的身份证号可以标识一个进程。PPID(Parent Process ID)父进程的ID表明“我是谁生的”。(2)fork();是一个函数可以用来创建子进程。#include unistd.h#include stdio.hint main() {pid_t pid fork(); // 这里分裂成两个进程if(pid0){//子进程会进入这里printf(子进程的PID:%d, PPID:%d\n,getpid(),getppid());}else{//父进程会进入这里i sleep(3);printf(父进程的PID:%d, PPID:%d\n,getpid(),getppid());}return 0;}fork是一个函数就会有返回值返回值 说明了什么-1 子进程创建失败当前处于父进程0 子进程创建成功当前处于子进程正数 子进程创建成功当前处于父进程当创建子进程成功后子进程会创建一个新的PCB内容拷贝自父进程的PCB但不是完全拷贝此时做为两个独立的进程会有各自不同的PID并且此时的拷贝为浅拷贝也就说明先共享等写时才会真正的复制。但是当子进程创建成功之后子进程的PPID会变成父进程的PID这也是父子两个进程之间的关联。上边提到了代码共享数据写时复制那这里实现一下看看效果#include unistd.h#include stdio.hint main() {int data200;printf(创建子进程之前的data:%d data地址:%p\n,data,data);pid_t pid fork(); // 这里分裂成两个进程if(pid0){//子进程进入这里data100;printf(创建子进程之后的子进程data:%d data地址:%p\n,data,data);}else{//父进程进入这里data0;printf(创建子进程之后的父进程data:%d data地址:%p\n,data,data);}return 0;}注意到data的值不同但是地址都是完全相同的所以大胆猜测一下就是这里的data地址不是真实的物理地址如果是真实的物理地址那么对应的data值应该完全相等才对。这就是写时拷贝(COW)。(3)写时拷贝(COW)谈这个不得不谈虚拟地址空间这个虚拟地址空间是伴随着进程的创建而创建(PCB和虚拟地址空间同时产生)随着进程销毁而销毁当我们的父进程创立之后就会通过页表(真实存在的数据结构并且不同的进程页表是独立的)建立虚拟地址到物理内存的映射关系然后当有了这个虚拟地址空间我们就可以找到对应的物理内存并把磁盘的代码和数据加载到内存之中。下面看一段代码还有现象。int main() {int data200;printf(创建子进程之前的data:%d data地址:%p\n,data,data);pid_t pid fork(); // 这里分裂成两个进程if(pid0){//子进程进入这里printf(创建子进程之后的子进程data:%d data地址:%p\n,data,data);}else{//父进程进入这里data0;printf(创建子进程之后的父进程data:%d data地址:%p\n,data,data);}return 0;}在这里会发现父进程修改的数据不会影响子进程但是data地址还是一样的这也就更加证明了这里的地址是虚拟内存地址。由于父进程先于子进程创建此时操作系统会产生一个父进程的虚拟内存地址并且通过页表将虚拟内存地址于物理内存相对应此时子进程创建会复制父进程的虚拟内存地址(是完全独立的)此时这个虚拟内存地址会指向同一片物理空间所以这就是为什么说父子进程之间资源共享。但是当我们的父进程修改之后不会影响子进程是因为修改是将物理空间的数据复制然后修改父进程对应的页表通过新的映射找到新的物理空间然后将数据加载到内存中后修改原来旧的数据还在原来的物理空间所以我们可以看到修改父进程不影响子进程同时可以猜想到子进程不影响父进程。所以可以得出一个结论就是发生写入操作之后就会分配新的物理页面页表的映射关系也会单独更新。(4)fork的返回值上文提到了fork是一个函数那么就会有返回值我们可以注意到fork返回值有0-1正整数并且在代码中会注意到返回了两个值当fork返回0的话子进程就会进入这里。当fork大于0的时候就会进入else那里并且会执行子进程的代码和父进程的代码所以不妨猜测一下这里的PID会返回两个值一个是0还有一个大于0的值分别进入不同的判断条件。为什么会返回两个值呢我们说fork是一个函数是函数就会有返回值(除了void之类的)当我们执行return的时候其实fork的任务已经完成了也就是说子进程已经创建好了父子进程共享代码此时在执行return之前的我们已经有了父子两个进程同时在执行到return语句的时候是两个进程共享的所以会返回两个值。看一段代码看现象。#include unistd.h#include stdio.hint main() {pid_t pid fork(); // 这里分裂成两个进程if(pid0){//子进程会进入这里printf(子进程的fork返回值:%d,PID:%d,PPID:%d\n,pid,getpid(),getppid());}else{//父进程进入这里printf(父进程的fork返回值:%d,PID:%d,PPID:%d\n,pid,getpid(),getppid());}return 0;}子进程的fork返回值是0没有问题父进程的fork返回值是子进程的PID这里的作用是每一个进程都有唯一的PID同时一个父进程可以有多个子进程为了标识不同的子进程才用了子进程的PID去标识同时这也是为后文介绍僵尸进程孤儿进程做一个准备。至于为什么同一个id既可以0又可以大于0就是后边说的了。后记后续将会更新Linux操作系统的进程状态。
返回列表