目录
1.3 PCB内部,属性都有哪些?(以 task_struct 为例)
4.2.那么进程在排队的时候是谁在排队呢?又是怎么样排队的呢?
4.2.1task_struct在排队的时候一般都不会只在一个数据结构里面,task_struct可以在多个数据结构内部进行排队。
4.2.2在排队的队列中只有内部类的信息,那么又是怎么找到PCB的地址的
1.描述进程的类---PCB
1.1 PCB进程管理模块(是什么?)
我们可以启动多个应用程序--->也就是可以把多个exe文件加载到内存里面。
那么操作系统要不要把内存里exe文件管理起来? 要的
那么如何描述呢?先描述,再组织
我们可以启动多个应用程序--->也就
为什么每一个程序加载到内存里都要生成一次PCB对象呢?
因为操作系统要进行管理。
在操作系统这门学科中 PCB是一个统称;
每一个具体的操作系统都有他们各自的PCB。
如Linux的PCB就叫task_struct
1.2什么是进程
未来:所以对于进程的控制与操作,都是对于PCB模块的处理,与可执行程序无关。
1.3 PCB内部,属性都有哪些?(以 task_struct 为例)
task_struct---PCB的一种
在Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
task_ struct内容分类:
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息.......
1.3.1 关于程序计数器的粗略介绍
程序计数器本质就是存储着下一条指令的地址,让CPU可以舒适的待在 取指令和执行指令的循环中
2.Linux程序关于进程的操作
2.1在Linux环境下如何去查看进程 : ps axj
根据指令也是进程这个现象,我们得出结论:所有我们用的独立的指令,都是程序,在运行的时候都会变成进程
我们发现就是当程序运行起来的时候,进程才会出现。当程序结束的时候进程也就消失了。所以,我们得出结论 : 进程也是有生命的。
2.2那么在代码内部我们如何去获取自己的进程号呢?
我们先明白我们要获取的是pcb内部的属性pid。
内核数据结构 + 可执行程序 =进程
所以,我们要获取的内容是属于操作系统内核的。
所以,我们需要用到操作系统内核提供的系统调用接口--------getpid
2.3一般来说,在操作系统内部,普通进程都有父进程
我们发现,每次我重新启动程序,虽然我的进程每次都是不一样的。但是,我的父进程每次都是一样的。
我们查看了一下这个进程,发现23616是一个叫bash 的进程。
我们得出结论:操作系统内部所有在命令行启动的进程都是这个bash的子进程 。
bash : 命令行解释器。
2.4查看进程的另一种方式
其实上,在Linux下,他还把进程以文件的形势储存在 /proc目录下,进程文件以进程自己的pid命名。
我们稍微看一部分,前面就是一些文件属性,后面的蓝色字体就是文件名,也就是进程pid
2.5查看一下进程文件内部信息
我们就简单看一 cwd :当前工作目录
exe:可执行文件所处位置
3.创建进程:系统调用接口fork()
从前面来看我们创建进程都是用命令解释器来运行可执行文件,从而创建了进程。
那么,我们可不可以写出一段代码,让可执行文件创建出,除自己外的进程呢?
3.1 fork(),创建进程
我们发现就是,使用了fork函数之后,产生了一个新的进程,并且,新进程是旧进程的子进程。
同样的,我们发现fork()之后,两个进程进行的操作是一样的,
所以我们得出第一个结论:fork()之后,代码共享。
3.2 fork函数的返回值
直接使用 man 指令
如果成功创建进程,子进程的pid被传给父进程,0被传给子进程
如果失败,返回给父进程-1,没有子进程被创建,设置errno。
3.3进程分流
一般来讲,我们希望子进程和父进程能够实现不同的操作。
所以根据返回值不同,我们可以用if语句进行判断,让父子进程做不同的操作。
3.4.fork()函数实现创建进程的基本流程
首先,我们知道 进程 = 内部数据结构 + 可执行程序和数据
生成的子进程中,内部数据结构的数据(task_struct) 就是以父进程的数据为模版写的,
当然pid和ppid肯定是用自己的。
而其中的可执行程序和数据则是二者共享。
3.5那么子进程自己要修改数据怎么办呢?
就是将要改变的数据,在申请一块空间,用于存储修改后的值
3.6关于fork()函数的返回值的三问
3.6.1 为什么给父进程返回pid,而给子进程返回0.
其实,是因为每个进程能有很多子进程,只能有一个父进程。父进程:子进程=1:n
所以,需要给父进程返回子进程的pid作为标识,而子进程的父进程是唯一的,所以返回0就行了。
3.6.2 fork()函数为什么能返回两次
return函数一般是函数的最后一步,前面的步骤一般都已经完成了,也就函数的核心逻辑已经完成了。那么就是,子进程也已经创建出来了。返回的本质其实上也就是写入。
所以,也就是在两个不同的进程中,返回语句都被执行了,所以就是了。
3.6.3 id作为一个变量为什么会同时有两个值
首先,我们要先明白一个点,就是一个进程的崩溃并不会影响到另一个进程。
进程任意之间都是相互独立的,并不会相互影响。
所以OS为了保证这一点呢,就设计一个变量可以代表两个地址 。
id这个变量在父进程标识一个地址,而在子进程又标识另一个地址。
4.进程状态
4.1.关于进程排队的问题
进程 = task_struct + 可执行程序和数据
4.2.那么进程在排队的时候是谁在排队呢?又是怎么样排队的呢?
之前,我们就提到了,进程在排队的时候就是task_struct在排队 。(只要是排队,一般都是task_struct在排队)
4.2.1task_struct在排队的时候一般都不会只在一个数据结构里面,task_struct可以在多个数据结构内部进行排队。
利用内部类,同时在不同的数据结构中排队。
每一个内部类都可以代表一个PCB在数据结构中排队。
4.2.2在排队的队列中只有内部类的信息,那么又是怎么找到PCB的地址的
PCB的地址是根据内部类自己本身的地址,与内部类自身与task_structd的偏移量计算得出。
4.3进程的三个状态 :运行,阻塞,挂起
我们要明白一点就是,进程不是一直在运行的,
即使他已经被加载到了CPU上(时间片:当一个进程占据了CPU大概一毫秒时,就会被踢出)。
像上面这个程序,就有可能是已经被加载到了CPU上了 ,开始运行了,但是他在等待你的输入,所以,他此时就停下来了。
进程的状态体现到代码上也可能就是task_struct内部的一个整型变量
进程状态这个属性又决定了什么事情呢?决定了你PCB下一步干什么事情。
4.3.1运行状态
运行状态:其实就是该进程随时准备好了被调度。此时的PCB会被链接到CPU的运行队列上。
此时,PCB是被链接在两个数据结构内部的。
4.2.2阻塞状态
就是当我们的进程在等待软硬件资源的时候,但是软硬件资源却没有准备好的时候,此时这个进程就不在适合在CPU的运行队列里排队。
PCB就会进行如下两步:
1.将自己的状态设置为阻塞;
2.排队不在排在运行队列,将自己排在等待的软件件资源的PCB的后面。
以硬件为例
我们知道,硬件也是被OS管理的,也是符合先描述后组织。
所以,操作系统,也会管理着描述硬件的类。
进程一开始会在CPU的运行队列里进行排队。
然后,在等待软硬件资源的时候,就会把自己设置成阻塞状态,然后,链接到硬件之后。
就好比是,前面的scanf函数,那个可执行程序的进程就会等待键盘资源,然后 在继续执行。
进程状态的变迁,就会引起OS将PCB变迁到不同的队列当中。
4.2.3挂起状态
挂起状态,一般只会发生计算机内存资源比较吃紧的情况,此时,操作系统将一些还不是马上就要被使用的可执行文件,先移除内存,放到磁盘上,空出部分资源以供使用,防止操作系统直接挂掉。
用于唤入和唤出的磁盘空间,就称为swap分区。
关于swap分区的设置,一般就设置为和内存差不多大就行了。
如果设置得太大了,内存太依赖这个空间,频繁的进行唤入和唤出,会导致效率降低
如果设置的太小了,不够用。
5.Linux下具体的进程状态
进程的状态:具体到代码的上,其实就是一个变量。
R 运行状态( running ) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。S 睡眠状态( sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep ))。D 磁盘休眠状态( Disk sleep )有时候也叫不可中断睡眠状态( uninterruptible sleep ),在这个状态的进程通常会等待IO 的结束。T 停止状态( stopped ): 可以通过发送 SIGSTOP 信号给进程来停止( T )进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。Z僵死状态( Zombies )是一个比较特殊的状态。当进程退出并且父进程(使用 wait() 系统调用 , 后面讲)没有读取到子进程退出的返回代码时就会产生僵死( 尸 ) 进程僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入 Z 状态X 死亡状态( dead ):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
5.1.S睡眠状态和D磁盘休眠状态
S睡眠状态(sleeping): 意味着进程在等待事件完成,这种情况就相当于我们之前提到的阻塞状态。
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
如果,在这种等待情况下,将PCB设置为S状态,当这个PCB在等待的过程中占据了太多资源,这个进程很有可能被OS杀死。
被杀死后,磁盘写完的时候返回信息,找不到人返回了 ,或者写一半找不到PCB了,那么就可能导致文件丢失。
所以就有了 D磁盘休眠状态 ,这个状态也是阻塞状态的一种,但是,却不会被OS杀死。
5.2 T停止状态(stopped)
这里的停止状态有两种:T和t
这里的T就相当于进程直接被停止了,如该进程被 kill -9 直接杀死了。
而t就是一个被追踪的停止状态,就相当于该可执行程序被gdb被调试的时候。
5.3 Z僵死状态(Zombies)
僵死状态( Zombies )是一个比较特殊的状态。当进程退出并且父进程(使用 wait() 系统调用 , 后面讲)没有读取到子进程退出的返回代码时就会产生僵死 ( 尸 ) 进程僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入 Z 状态
僵尸进程危害
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z 状态?是的!维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在 task_struct(PCB) 中,换句话说,Z 状态一直不退出, PCB 一直都要维护?是的!那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C 中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!内存泄漏 ? 是的
5.4 死亡状态X
死亡状态X:意味着进程已经终止,且资源已经被回收,它只是一个返回状态,不再存在于进程列表中。
6.孤儿进程
父进程如果提前退出,那么子进程后退出,进入 Z 之后,那该如何处理呢?父进程先退出,子进程就称之为 “ 孤儿进程 ”孤儿进程被 1 号 init 进程领养,当然要有 init 进程回收喽。
我们这里看到其实上1号进程就是操作系统。