Linux 进程

进程

含义:

进程是一个程序执行的过程,会去分配内存资源,具有并发特性(同一时段,运行多个任务)。

进程的基本特性:

(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 会关闭所有的已经打开的文件,不执行清理函数。

  1. 主线程退出

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;
}
相关推荐
咖喱鱼蛋20 分钟前
Ubuntu安装Electron环境
linux·ubuntu·electron
ac.char24 分钟前
在 Ubuntu 系统上安装 npm 环境以及 nvm(Node Version Manager)
linux·ubuntu·npm
肖永威30 分钟前
CentOS环境上离线安装python3及相关包
linux·运维·机器学习·centos
tian2kong33 分钟前
Centos 7 修改YUM镜像源地址为阿里云镜像地址
linux·阿里云·centos
布鲁格若门37 分钟前
CentOS 7 桌面版安装 cuda 12.4
linux·运维·centos·cuda
C-cat.44 分钟前
Linux|进程程序替换
linux·服务器·microsoft
dessler44 分钟前
云计算&虚拟化-kvm-扩缩容cpu
linux·运维·云计算
怀澈1221 小时前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++
DC_BLOG1 小时前
Linux-Apache静态资源
linux·运维·apache
学Linux的语莫1 小时前
Ansible Playbook剧本用法
linux·服务器·云计算·ansible