目录
1. 进程的基本概念与基本操作
1.1 总结:什么才是进程
2. 描述进程-PCB(process control block)
2.1 task_ struct
2.2 task_ struct内容分类
2.3 组织进程
3. PID
获取当前进程PID - getpid
获取父进程PID - getppid
如何查看进程
1 进程的信息可以通过 /proc 系统⽂件夹查看
2 我们还可以使⽤top和ps这些⼯具来获取
4. fork - 创建子进程
4.1 为什么fork要给父进程返回子进程的pid, 给子进程返回0呢?
4.2 fork函数为什么会返回两次?
总结:
1. 进程的基本概念与基本操作
课本概念:程序的⼀个执⾏实例,正在执⾏的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体
但是,上面的内容对于我们初学者来说是非常难以理解的,所以我们可以画图来进行理解
一个程序运行起来叫做进程,未运行起来就是一个二进制文件,存储在磁盘当中,程序运行之前要加载到内存,在操作上就是(./cmd) ,而真正意义当我们执行时cmd就会加载到内存当中去
这里我们是把一个程序加载到内存,有没有可能我们在操作系统里,在同一时刻我们可以把成百上千给程序加载到内存里呢?答案是一定的
而有一款软件是最开始就加载进来了,叫做操作系统
如上图,我们可以看到在内存中,有很多加载的程序,那么我们就有一个问题:这些程序在内存中的什么位置被加载?代码和数据是否已经被cpu已经执行完毕呢?有没有可能其中的程序需要暂停运行?有没有可能其中内存不够了,需要扩容呢?
所以在内存中有这么多加载的内存,它们都需要申请内存和加载内存,所以操作系统要对这些进程,这些代码进行管理,但是我们可以做管理吗?答案是不能,因为操作系统虽然知道里面有代码,但是操作系统并不知道是那个进程
但是我们有没有解决方法呢?答案是先描述,再组织
操作系统会在操作系统内部(内核)给每一个代码和数据构建一个struct结构体,然后每加载进来一个程序,操作系统就为该程序创建一个同类型的对象,然后把当前这个进程按照创造的对象填写好信息,填好之后就有了一个对应的节点,而这里每一个节点都有对应的指针可以指向对应的代码和数据
与此同时,我们对应的每个节点当中,它的指针还可以指向它的下一个节点,最终在操作系统内我们形成了一个程序列表,我们把这个程序列表叫做进程列表
1.1 总结:什么才是进程
2. 描述进程-PCB(process control block)
其实我们上面已经见过PCB了,就是我们上面创建的结构体,在操作系统中所有表示进程的都叫PCB,而这个结构体叫做进程控制块
进程控制块里包含进程的所有属性,进程的所有属性都可以直接或者间接通过 task_ struct找到
进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合
所以为什么一个进程加载的时候操作系统要给它创建一个对应的PCB即task_ struct对象呢?
因为操作系统要管理进程,所以要先描述,再组织,所以必须要有描述进程的task_ struct再组织管理成数据结构,操作系统就会转化成对应数据结构的增删查改
操作系统要对进程做管理,那么就必须要对进程的PCB做管理
2.1 task_ struct
Linux中进程控制块PCB-------task_struct结构体结构 - 童嫣 - 博客园https://www.cnblogs.com/tongyan2/p/5544887.html
1. 在Linux中描述进程的结构体叫做task_struct
2. task_struct是Linux内核的⼀种数据结构,它会被装载到RAM(内存)⾥并且包含着进程的信息
2.2 task_ struct内容分类
1. 标示符: 描述本进程的唯⼀标⽰符,用来和其他进程进行区别
2. 状态: 任务状态,退出代码,退出信号等
3. 优先级: 相对于其他进程的优先级
4. 程序计数器: 程序中即将被执⾏的下⼀条指令的地址
5. 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
6. 上下文数据: 进程执⾏时处理器的寄存器中的数据[休学例⼦,要加图CPU,寄存器]
7. I/O状态信息: 包括显⽰的I/O请求,分配给进程的I∕O设备和被进程使⽤的⽂件列表
8. 记账信息: 可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等
9. 其他信息
2.3 组织进程
所有运⾏在系统⾥的进程都以task_struct链表的形式存在内核里
在linux内核中, 最基本的组织进程task_struct的方式, 是采用双向链表进行组织的
3. PID
获取当前进程PID - getpid
进程创建的时候, 里面都有一个自己的PID,我们如何在一个程序运行时获取这个程序的PID呢?
如图所示,操作系统里面上层是系统调用, 下层是内存缓冲区, 这个时候内存中已经缓存了两个进程,PCB对象里面含有PID,我们可以使用ps axj和管道来获取进程的PID
但是我们都知道,操作系统不相信我们用户, 所以我们就不能直接访问PCB(task_struct)也就是里面的PID, 状态等, 想要获取这些字段就必须使用系统调用接口
如果我们想要获取当前进程的PID的系统调用接口的话,我们就要使用getpid(), 这个函数在哪个进程里被调用, 就会返回哪个进程的PID
注意:pid是一个整形, 下面是我们自己定义的一个获取系统调用接口的程序
当前进程为:
获取父进程PID - getppid
对上面的概念进行试验之后, 我们再来看一下父进程, 也就是PPID
我们发现,父进程ppid都是不改变的,而pid每次都是变化的,这是为什么呢?
我们联想到王婆的例子
王婆不想给自己的牌子砸了,所以就找了些实习生去解决,不需要王婆亲自去解决了,派实习生去解决,所以我们的-bash就是王婆(父进程)
1.运行一个进程时,系统会自动创建bash进程
2.命令行再执行所有的程序或者指令时,它所对应的进程,所对应的父进程就是bash本身。我们自己执行的程序或者指令都是bash进程的子进程
3.执行出问题的时候,只会是子进程出问题,不会影响bash进程
4.我们启动xshell时候,系统自动生成bash进程,显示命令行
如何查看进程
1 进程的信息可以通过 /proc 系统⽂件夹查看
如:要获取PID为1的进程信息,你需要查看 /proc/1 这个⽂件夹
proc文件夹里面的目录都是临时文件,当进程开始就会创建一个以这个进程的pid作为名字的文件夹,进程结束的时候就会删除这个文件夹
我们发现: 这些进程都是目录, 并且这些目录的名字都是数字
我们可以通过proc文件来查看这个进程更详细的信息
2 我们还可以使⽤top和ps这些⼯具来获取
ps:
top:
在命令行中,执行命令/执行程序,本质是bash的进程,创建子进程,来执行我们的代码
4. fork - 创建子进程
fork是一个系统调用,fork没有参数,有两个返回值
父子进程代码共享,数据各⾃开辟空间,私有⼀份(采用写时拷贝)
fork函数的本质就是是一个系统调用
上面那张图意思就是说如果fork函数成功了, 那么给父进程返回子进程的pid, 0返回给子进程。 如果失败了, 就返回 -1 给父进程。 并且没有子进程被创建
也就是说, fork有两个返回值, 并且这两个返回值的类型都是pid_t, 也就是有符号整形
运行结果:
我们可以看到每一秒打印一条父进程, 打印一条子进程,这说明父进程和子进程是同时进行的,并且id > 0, 和 id == 0同时成立, 如果在其他的代码中, 这两种情况不可能同时存在,但是在调用的fork下就可以
所以在我们fork之后所有的代码都是共享的,只不过父进程认为自己>0,子进程认为自己=0,所以父进程会进入第三个,子进程会进入第二个,所以我们就可以做到父子执行不同的代码块
到了这里,我们有几个问题
4.1 为什么fork要给父进程返回子进程的pid, 给子进程返回0呢?
在我们的操作系统里,我们的父进程比上子进程是1:N的,简单来说就是任何一个父进程可以有一个或者多个子进程
所以我们在创建子进程时,一定要把子进程的pid返回给父进程,因为父进程需要通过返回的不同的pid来区分不同的子进程,而子进程不需要获得父进程的pid,因为子进程已经能够获得getppid了,所以子进程只需要表明自己成功建立就可以了
4.2 fork函数为什么会返回两次?
在这个问题之前我们先讨论另一个问题:一个函数执行到return时,函数的核心功能做完了吗?答案是核心功能已经完成了
fork函数的本质就是是一个系统调用
如图所示:fork函数创建子进程后, 函数后面的代码就会被子进程和父进程所共享
当fork函数里面创建好子进程后(绿色方框部分) 子进程就被创建出来了, 然后执行流就变成了两个(可以使用if或者else或者else if来进行分流), 一个子进程的执行流, 一个父进程的执行流
也就是说, 这个时候的return语句, 其实就是由两个执行流会执行它,因为return也是语句,所以会被执行两次,而这两个执行流都会返回一个值,所以这就是为什么fork会有两个返回值, 并且返回值给一个给子进程, 一个给父进程的原因
总结:
1. 进程具有独立性,简单来说就是一个进程挂掉了并不会影响其他进程,哪怕是父进程挂掉了也不会影响子进程
如果子进程和父进程公用一个数据块, 当子进程改变数据的时候, 父进程也会改变数据,所以 不能让父进程和子进程共享一份数据
对于子进程来说数据是独立的,所以当创建子进程的时候要拷贝一份父进程的数据独立出来
这个时候父进程崩溃或者子进程崩溃都不会影响对方
未完待续~