概念
进程指的是程序在执行过程中的活动。进程是操作系统进行资源分配和调度的基本单位。
进程可以看作是程序的一次执行实体,它包含了程序代码、数据以及相关的执行上下文信息。操作系统通过创建、调度和管理多个进程来实现对计算机系统资源的有效利用。
每个进程都有自己的地址空间、寄存器集合、堆栈等资源,他们与其他进程相互隔离。不同的进程之间可以通过进程间的通信(PIC)机制进行信息交换和数据共享。
简单的来说,进程就是加载到内存的程序。
PCB进程控制块
那么如何管理加载到内存的程序呢?
答案是先描述再组织。
而这样管理进程的结构体,我们称之为进程控制块(PCB)。
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合
PCB与进程之间存在着一对一的对应关系,每个进程在系统中都有一个相应的PCB来描述和管理它。当操作系统创建一个新进程时,会为该进程分配一个独立的PCB。
PCB通过维护这些信息,操作系统可以管理和控制进程的状态转换、调度和资源分配。当系统需要切换进程时,它会保存当前进程的上下文信息到该进程的PCB中,然后加载新进程的PCB,并将保存的上下文信息恢复, 从而实现进程间的无缝切换和调度。
PCB的属性(task_struct)
在Linux中,描述进程的结构体叫作task_struct(也就是Linux的PCB)。
查看进程
通过指令
ps ajx
进行查看
PID是进程的唯一标识符
下面写一个简单的C程序来看一下进程
结果查看:
上面进程中,我们执行指令grep也是进程的一条,当我们查看时,可以忽略他,加上:
我们可以利用while循环,来不断查询当前的进程情况:
获取PID/PPID
PID:是进程的唯一标识符,类似于你在学校专属的学号,可以通过PID查看到对应的进程信息。
在C程序我们也可以通过函数来获取PID
PPID被称为子进程的父进程;当一个进程之中再有一个进程在运行,那么这个在里面的进程被称为子进程,而外面的进程称为父进程;
proc
对proc的查看
执行指令 ls /proc/PID -ld
对执行程序的查询,再将程序退出查询,对比两次的情况
执行指令 ls /proc/PID -l
查看进程的总信息
列举一些比较关键的信息
利用文件的生成查看对应的工作目录(默认在对应的进程工作目录里生成)
更改当前的工作目录时,
创建进程–fork()
fork()是一个系统调用,为当前运行进程创建一个新的子进程。在调用fork之后,操作系统会复制当前进程的所有资源(包括代码、数据、堆栈等),并创建一个全新的进程。这个进程被称为子进程,而原来的进程被称为父进程。
在fork操作完成后,父进程和子进程是相互独立的,它们有各自独立的内存空间和资源。子进程会继承父进程的属性,例如文件描述符,信号处理程序等。
接下来我们看操作实例:
fork的返回值
俩进程独立操作
利用kill指令杀掉父进程:
一次创建多个进程
结果:
进程状态
进程状态描述了一个进程在执行过程中的不同状态。
常见的进程状态包括:
R运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
D磁盘休眠状态(Disk sleep有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
我们还是利用指令 ps ajx 来查看进程的状态。
如:
细节:
kill的常用指令
僵尸进程
僵尸进程是一个处于已终止但仍然存在于进程表中的进程。当一个子进程运行结束后,它会向父进程发送一个终止信号。
如果父进程没有及时处理子进程的终止状态,子进程的PCB就会一直存在于系统的进程表中,成为一个僵尸进程。僵尸进程所占用的系统资源非常有限,仅仅保留了它的进程ID、返回值和一些基本信息。
下面来演示下僵尸进程
将子进程优先退出,父进程进入睡眠状态,子进程退出之后,父进程仍然在睡眠状态没有回收子进程的资源;
int main() { pid_t id = fork(); if(id < 0){ perror("fork"); return 1; } else if(id==0) { printf("child[%d] is begin Z...\n", getpid()); sleep(5); exit(1); } else { printf("parent[%d] is sleeping...\n", getpid()); sleep(30); } return 0; }
结果:
僵尸进程的危害
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎
么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话
说, Z状态一直不退出, PCB一直在维护。
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构
对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
孤儿进程
父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,由init进程回收。
例子:
通过让父进程优先退出,来查看进程状态
7 int main() 8 { 9 pid_t id = fork(); 10 if(id < 0){ 11 perror("fork"); 12 return 1; 13 } 14 else if(id==0) 15 { 16 printf("child[%d] is begin Z...\n", getpid()); 17 sleep(15); 18 19 } 20 else 21 { 22 printf("parent[%d] is sleeping...\n", getpid()); 23 sleep(5); 24 exit(1); 25 } 26 return 0; 27 }
结果:
进程优先级
进程优先级是指操作系统为每个进程分配的执行优先级,用于控制和管理多任务环境中的进度调度。通过设置不同的优先级,可以决定哪些进程应该优先执行,从而影响系统的响应时间和吞吐量。