进程
含义:
进程是一个程序执行的过程,会去分配内存资源,具有并发特性(同一时段,运行多个任务)。
进程的基本特性:
(1)动态性:进程是程序的一次执行,它有着创建、活动、暂停、终止等过程,具有一定的生命周期,是动态地产生、变化和消亡的。动态性是进程最基本的特征。
(2)并发性:指多个进程实体同时存于内存中,能在一段时间内同时运行。并发性是进程的重要特征,同时也是操作系统的重要特征。引入进程的目的就是为了使程序能与其他进程的程序并发执行,以提高资源利用率。
(3)独立性:指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单元。凡未建立PCB的程序都不能作为一个独立的单元参与运行。
(4)异步性:由于进程的相互制约,使得进程具有执行的间断性,即进程按各自独立的、不可预知的速度走走停停、何时开始何时暂停何时结束不可预知的性质,就会导致执行结果的不可再现性,为此在操作系统中必须配置相应的进程同步机制。
(5)结构性:每个进程都配置一个PCB对其进行描述。从结构上看,进程实体是由程序段、数据段和进程控制块三部分组成的。
进程的组成
从结构上看,进程实体是由程序段 、数据段 和进程控制块三部分组成的。
进程控制块 PCB :每个进程有且仅有一个进程控制块(Process Control Block,PCB),或称作进程描述符,它是进程存在的唯一标识,是操作系统用来记录和刻画进程状态及环境信息的数据结构,也是操作系统掌握进程的唯一资料结构和管理进程的主要依据。所以 PCB 是提供给操作系统使用的,操作系统需要对各个进程进行管理,所有管理时所需要的信息,都会被放在 PCB 中。创建进程和撤销进程等都是指对 PCB 的操作,当进程被创建时,操作系统为其创建 PCB,当进程结束时,会回收其 PCB。
进程控制块 PCB 一般包含如下四类信息:
1)进程描述信息:用来让操作系统区分各个进程,当进程被创建时,操作系统会为该进程分配一个唯一的、不重复的 "身份证号"--- PID(ProcessID,进程 ID)。另外,进程描述信息还包含进程所属的用户 ID(UID)
2) 进程控制和管理信息:记录进程的运行情况。比如 CPU 的使用时间、磁盘使用情况、网络流量使用情况等。
3)资源分配清单:记录给进程分配了哪些资源。比如分配了多少内存、正在使用哪些 I/O 设备、正在使用哪些文件等。
4)CPU 相关信息:进程在让出 CPU 时,必须保存该进程在 CPU 中的各种信息,比如各种寄存器的值。用于实现进程切换,确保这个进程再次运行的时候恢复 CPU 现场,从断点处继续执行。这就是所谓的保存现场信息。
数据段:一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行时产生的中间或最终结果,即进程运行过程中的各种数据(比如程序中定义的变量)。
程序段:程序段就是能被进程调度时程序调度到CPU执行的程序代码段(指令序列)。注意,程序可被多个进程共享,即多个进程可以运行同一个程序。例如:同时挂三个 QQ 号,会对应三个 QQ 进程,它们的 PCB、数据段各不相同,但程序段的内容都是相同的(都是运行着相同的 QQ 程序)
注意:Linux内核使用task_struct数据结构作为PCB的具体实现。task_struct包含了进程的多种属性,如进程标识符(PID)、状态、优先级、程序计数器、内存指针、上下文数据、I/O状态信息等。这些信息帮助操作系统管理和调度进程
进程和程序的区别
程序:静态
存储在硬盘中代码,数据的集合
进程:动态
程序执行的过程,包括进程的创建、调度、消亡
1)程序是永存,进程是暂时的
2)进程有程序状态的变化,程序没有
3)进程可以并发,程序无并发
4)进程与进程会存在竞争计算机的资源
5)一个程序可以运行多次,变成多个进程
一个进程可以运行一个或多个程序
内存的分布(以32位为例)
0-3G,是进程的空间,3G-4G是内核的空间,虚拟地址(总的内存为4GB)。
虚拟地址 * 物理内存和虚拟内存的地址 映射表 1page=4k
内存分布图:
进程分类
1、交互式进程
2、批处理进程 (批处理进程是指那些不需要用户干预即可自动执行的进程。它们通常用于执行一系列预先定义好的任务或脚本。)
3、 守护进程 (大部分时间处于休眠状态,满足某些特定条件才运行)
进程的状态:
3个状态,就绪→执行态→阻塞(等待,睡眠)基本操作系统
linux中的状态,运行态,睡眠态,僵尸态,暂停态。
进程的调度:
进程调度是操作系统内核的一个重要功能,它负责决定哪个进程获得处理器时间以及何时进行切换。(宏观并行,微观串行)
cs
1.调度队列(Ready Queue):
就绪队列包含所有可运行的进程,它们已经准备好被CPU执行。
2.调度策略(Scheduling Policy):
操作系统采用不同的调度策略来决定哪个进程应该获得CPU时间。常见的调度策略包括先来先服务(FCFS)、最短作业优先(SJF)、轮转(Round-Robin)、优先级调度等。
3.上下文切换(Context Switch):
当前运行的进程被挂起,CPU的状态(如寄存器内容)被保存到该进程的PCB中。然后,调度程序加载下一个要运行的进程的上下文到CPU。
4.调度算法(Scheduling Algorithm):
调度算法是一组规则,用于决定进程调度的顺序。不同的算法有不同的性能指标,如吞吐量、响应时间、等待时间和周转时间。
5.抢占式调度(Preemptive Scheduling):
在抢占式调度中,高优先级的进程可以中断当前正在运行的进程,抢占CPU时间。
6.非抢占式调度(Non-Preemptive Scheduling):
在非抢占式调度中,一旦一个进程开始执行,它将运行直到完成或主动放弃CPU。
7.优先级(Priority):
进程被赋予不同的优先级,调度器根据优先级来选择下一个要执行的进程。
8.时间片(Time Quantum):
在时间片轮转调度中,每个进程被分配一个时间片,即它在CPU上允许运行的时间。
9.多级队列(Multilevel Queue):
操作系统可能使用多个队列来管理不同类别的进程,每个队列都有自己的调度算法。
10.多级反馈队列(Multilevel Feedback Queue):
一种复杂的调度机制,允许进程在多个优先级队列之间移动,根据其行为动态调整优先级。
11.调度决策(Scheduling Decisions):
调度器需要做出的决策包括哪个进程将获得CPU,何时进行上下文切换,以及如何调整进程的优先级。
12.调度器(Scheduler):
调度器是操作系统内核的一部分,负责执行进程调度的算法和策略。
进程相关命令
1.ps aux
查看进程相关信息
进程的当前状态
cs
D: Uninterruptible sleep(不可中断睡眠状态) - 进程正在等待某些事件的完成,通常是 I/O 操作。在这种状态下,进程不能被中断。
R: Running or runnable(运行或可运行状态) - 进程正在使用 CPU 或者在运行队列中等待 CPU 时间。
S: Interruptible sleep(可中断睡眠状态) - 进程正在等待某个事件的发生,并且可以被信号中断。
T: Stopped(停止状态) - 进程已经停止执行,通常是因为接收到了一个暂停信号(如 SIGSTOP)。
t: Traced or stopped by debugger(被跟踪或由调试器停止) - 进程被调试器控制或因为某些调试操作而停止。
W: Paging(分页状态) - 这个状态在 Linux 2.6 版本之后的内核中不再有效。
X: Dead(死亡状态) - 进程已经结束,但不应该会在 ps 命令的输出中看到。
Z: Defunct("僵尸"状态) - 进程已经终止,但父进程尚未调用 wait() 或 waitpid() 系统调用来读取其退出状态。
2.top
根据CPU占用率查看进程相关信息
3.kill,killall
kill
kill
命令用于向特定进程发送信号。它的使用格式通常是 kill PID
,其中 PID
是要接收信号的进程的进程标识符
cs
kill -2 (PID)15 //发送信号+PID对应的进程,默认接收者关闭
(1-64) (Id号)
(-2代表中止)
killall
killall
命令用于向所有匹配指定名称的进程发送信号。与 kill
不同,killall
通过进程名称而不是 PID 来识别进程。
cs
killall -9 进程名
发送信号 进程名对应的所有进程
eg:
killall a.out
进程相关的函数
1.fork();
在Linux操作系统中,fork()
系统调用是一种创建新进程的方式。这个调用非常特殊,因为它会复制调用它的进程,生成一个新的子进程。
返回值:int 类型的数字。
在父进程中:成功 返回值是子进程的 pid号 >0
失败 返回-1;
在子进程中:成功 返回值 0
失败 无
注意:
1.一次调用,会返回两次。
2.子进程先运行和是父进程先进程,顺序不确定。
3.变量不共享。
4.子进程复制父进程的0到3g空间和父进程内核中的PCB,但id号不同。
功能:通过该函数可以从当前进程中克隆一个同名新进程。
克隆的进程称为子进程,原有的进程称为 父进程。
子进程是父进程的完全拷贝。
子进程的执行过程是从fork函数之后执行。
5.子进程与父进程具有相同的代码逻辑。
举例:边看电视边吃饭,(处理一起运行的进程)
cs
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, const char *argv[])
{
pid_t ret=fork();
if(ret>0)
{
//father
while(1)
{
printf("吃饭\n");
sleep(1);
}
}
else if(0==ret)
{
//child
while(1)
{
printf("看电视呢\n");
sleep(1);
}
}
else
{
perror("fork error\n");
return 1;
}
return 0;
}
父子进程的关系:
子进程是父进程的副本。子进程获得父进程数据段,堆,栈,正文段共享。
在fork之后,一般情况那个会先运行,是不确定的。如果非要确定那个要先运行,需要IPC机制。
区别:
1)fork的返回值
2)pid不同
2.getpid
cs
pid_t getpid(void);
功能:
获得调用该函数进程的pid
参数:
缺省
返回值:
进程的pid
3.getppid
cs
pid_t getppid(void);
功能:
获得调用该函数进程的父进程pid号
参数:
缺省
返回值:
返回父进程id号
进程的终止:8中情况
1)main 中return
2)exit(), c库函数,会执行io库的清理工作,关闭所有 的流,以及所有打开的文件。已经清理函数(atexit)。
3)_exit,_Exit 会关闭所有的已经打开的文件,不执行清理函数。
- 主线程退出
5)主线程调用pthread_exit
异常终止
6)abort()
7)signal kill pid
8)最后一个线程被pthread_cancle
进程的退出
僵尸进程和孤儿进程
僵尸进程:进程执行结束但空间未被回收变成僵尸进程
cs
僵尸:
当一个子进程完成执行后,如果父进程没有通过wait()或waitpid()系统调用来获取子进程的退出状态,子进程的进程描述符(PCB)仍然保留在系统中,这种状态的进程被称为僵尸进程
僵尸进程的产生原因主要有两点:
子进程已经终止,但父进程没有调用wait()或waitpid()来回收子进程的资源。
父进程在子进程终止前退出,导致子进程成为孤儿进程,被init进程接管,但僵尸状态的子进程仍然存在
。
孤儿进程:
是指父进程已经终止,但子进程仍在运行的进程
1.exit 库函数
退出状态,终止的进程会通知父进程,自己使如何终止的。如果是正常结束(终止),则由exit传入的参数。如果是异常终止,则有内核通知异常终止原因的状态。任何情况下,负进程都能使用wait,waitpid获得这个状态,以及资源的回收。
cs
void exit(int status)
exit(1);
功能:
让进程退出,并刷新缓存区
参数:
status:进程退出的状态
返回值:
缺省
举例:
cs
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
FILE* fp = fopen("1.txt","w");
char buf[512]="hello,123";
fputs(buf,fp);
exit(0);
printf("aaaaaaaaaaa\n");
return 0;
}
return 当该关键字出现在main函数中时候可以结束进程
如果在其他函数中则表示结束该函数。
exit -> 刷新缓存区 -> atexit注册的退出函数 -> _exit
2._exit 系统调用
函数原型
cs
void _exit(int status);
功能:
让进程退出,不刷新缓存区
参数:
status:进程退出状态
返回值:
缺省
3.atexit (回调函数)
通常用于执行清理工作,比如释放资源或保存状态。
原型:
cs
int atexit(void (*function)(void));
功能:
注册进程退出前执行的函数
参数:
function:函数指针
指向void返回值void参数的函数指针
返回值:
成功返回0
失败返回非0
注意事项:
atexit
调用的函数不应该执行太长时间,因为它们会阻塞程序的退出。
程序员应该小心避免在退出函数中调用可能已经关闭的函数或资源。
过多的 atexit
调用可能会影响程序的性能,因为每个调用都需要系统来跟踪这些函数。
当程序调用exit或者由main函数执行return时,所有用atexit注册的退出函数,将会由注册时顺序倒序被调用
举例:
cs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
char * p ;
void clean(void)
{
printf("clean func,p %s\n",p);
free(p);
}
int main(int argc, char *argv[])
{
atexit(clean);
p = (char*)malloc(50);
strcpy(p,"hello");
printf("main p %s\n",p);
exit(0);
return 0;
}