文章目录
什么是进程
可执行程序加载到内存后就形成了进程,进程里包含了代码数据,运行状态等信息。
操作系统通过管理进程控制块PCB(task_struct)来管理可执行程序,进程担当分配系统资源(CPU时间,内存)的任务。
所以,只要可执行程序加载到内存,操作系统就会创建PCB将其管理,最后就形成了进程。
操作系统将每个PCB通过双向链表的方式链接起来,创建了一个新进程就直接将对应的PCB链入双向链表中,退出就是将PCB删除
tast_struct的内容
- 标示符(PID): 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器(pc指针): 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
- 上下文数据: 进程执行时处理器的寄存器中的数据。
进程里的代码不是一下子就全跑完的,一个进程会分配一个时间片,如果在时间片用完还没跑完代码,就会被暂时挂起,等待CPU下次的分配时间片再跑,同时切换到其他进程重复相同操作,只是切换的特别快,让用户以为是多个进程同时在执行。
查看进程
ps命令可以查看用户的所有子进程。
也可以查看某个进程的详细信息
创建进程
标识符
pid是进程的唯一标识符,用来区分不同进程。
可以用过系统调用函数getpid和getppid活着的进程的id和进程的父进程id
通过man命令查看getpid怎么使用(所需要包含的头文件)
写个打印pid和ppid的代码看一下
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
fork
while(1)
{
printf("proc PID :%d,parent PID:%d\n",getpid(),getppid());
sleep(1);
}
return 0;
}
可以直接ctrl+c或者在另一个终端上kiil -9 pid 杀死对应进程
fork
写一个fork举例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
printf("父进程开始运行,pid:%d\n", getpid());
fork();
printf("进程开始运行,pid:%d\n",getpid());
return 0;
}
fork()函数用于创建一个子进程,调用fork()函数的是父进程,所以就打印了两次倒数第二行。
值得一提的是:在子进程被创建之前的代码被父进程执行,而子进程被创建之后的代码,则默认情况下父子进程都可以执行。父子进程虽然代码共享,但是父子进程的数据各自开辟空间(采用写时拷贝)。
fork的返回值
虽然父子进程代码是共享的,但是两者是独立的进程,所以return语句会分别被父子进程执行,所以fork有两个返回值。
- 如果子进程创建成功,父进程就会得到子进程的pid,子进程中返回0(用于标识它是一个子进程)
- 如果子进程创建失败,则在父进程得到一个-1
值得一提的是,如果在另一台终端把父进程杀死了,子进程一样会继续运行。
进程状态
Linux进程状态有7种
static const char *const task_state_array[] = {
"R (running)", /*0 */
"S (sleeping)", /*1 */
"D (disk sleep)", /*2 */
"T (stopped)", /*4 */
"t (tracing stop)", /*8 */
"X (dead)", /*16 */
"Z (zombie)", /*32 */
};
在深入认识Linux7种状态前,先了解一下进程三大状态:
(一)运行态
运行态是指进程正在处理器上运行,它所对应的程序代码正在被 CPU 执行的状态,处于运行态的进程能够占用 CPU 资源,并按照程序的逻辑顺序依次执行指令,进行数据处理、与其他进程或系统进行交互等操作,是进程真正处于活动执行的状态
(二)阻塞态
阻塞态是指进程因等待某种事件的发生而暂时无法继续执行的状态,处于阻塞态的进程不能占用 CPU 资源,此时进程会暂停执行,直到所等待的事件完成或条件满足,才有可能重新进入就绪态或运行态(如scanf)
(三)挂起态
挂起态是指进程暂时被操作系统从内存中转移到外存(如磁盘)上,或者虽然仍在内存中但被标记为暂停执行的一种状态,处于挂起态的进程不再参与 CPU 的调度,其运行被暂停,直到满足特定条件后才可能恢复到其他可执行状态
运行状态
虽然运行状态能能够占用cpu资源,但不意味着一定占用,当一个进程处于运行状态,只表示它随之可以被调度,所以同时存在多个运行状态的进程是正常的。
浅度睡眠状态(可中断睡眠状态)
进程正在等待某个条件而处于阻塞状态,它是可以快速恢复执行的。
浅睡眠状态可以用kill指令杀死。
深度睡眠状态(不可中断睡眠状态)
进程正在等待I/O操作的完成,且不能被信号唤醒。这种状态通常发生在进程等待磁盘写入或读取操作时。这种状态会持续到完成操作之后,退出深度睡眠状态。
暂停状态
暂停状态最常见的就是调试下的断点,当调试时,在断点处停下,此时的进程状态就是t。
还有一种就是通过发送SIGSTOP(kill -STOP pid)停止进程,这个被暂停的进程可以通过发送SIGCONT(kill -CONT pid) 信号让进程继续运行。
死亡状态
死亡状态是指此进程已经终止执行且退出了,资源也已被释放。
僵尸状态
当一个进程退出时,该进程曾经申请的资源并不是立即被释放,而是要暂时存储一段时间,以供操作系统或是其父进程进行读取,如果退出信息一直未被读取,则相关数据是不会被释放掉的。所以进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态。
孤儿进程
孤儿进程顾名思义,就是当前进程的父进程已经执行结束,或者因为其他原因退出了,但是当前进程还没有被父进程处理,当前进程一直占用着资源。所以,当出现孤儿进程时,1号进程(系统进程)会将其领养。
僵尸进程
处于僵尸状态的进程就是僵尸进程,当子进程已经执行结束退出了,父进程却没有读取子进程的退出信息,为了维护还没被父进程读取的子进程信息,它会占用PCB。如果系统中存在大量的僵尸进程,就有可能会导致系统资源的枯竭(内存泄漏),进一步引发系统崩溃。
进程的优先级
优先级顾名思义就是获得资源的先后顺序。
为什么有优先级?因为资源有限,一个cpu一次只能跑一个进程,而进程不止一个。
可以通过ps -l 查看进程优先级
PRI:代表这个进程可被执行的优先级,数字越小优先级越高。在Linux操作系统当中,PRI(old)默认为80,即PRI = 80 + NI。
NI:代表这个进程的nice值。NI的取值范围是-20至19,一共40个级别。
修改优先级
可以用top命令,然后用方向键向下或者向上查找进程的pid,输入pid再按r修改pid,输入NI值,最后回车确认。记得按q退出top命令
Linux的进程调度算法
一个cpu只有一个runqueue
实时优先级:0~99
普通优先级:100~199(和NI值相对应[-20,19])
活动和过期队列的结构都一样
虽然队列里只有有140个进程,但是为了极致的效率,还用借助了位图用5*32个比特位表示队列是否为空(即一次检查一个int,可一次跳跃32个比特位).
活动队列(active queue)
活动队列维护了当前正在运行且其时间片尚未耗尽的进程。这些进程按照优先级被组织在队列中,等待CPU的调度执行。
过期队列(expired queue)
过期队列维护了那些时间片已经耗尽或由于其他原因被暂时挂起的进程。这些进程在等待下一次被调度执行时,会被放置在过期队列中。
active指针永远指向活动队列 expired指针永远指向过期队列。
当活动队列中的进程时间片耗尽时,它们会被移动到过期队列中,当活动队列的进程越来越少,过期队列的进程越来越多,在活动队列的进程都被执行完毕时,这两个指针会进行交换,使得原来的过期队列成为新的活动队列,继续被调度执行。
进程会被调度和切换,所以进程就是运行起来的程序!