欢迎来到Cefler的博客
博客主页:那个传说中的man的主页
个人专栏:题目解析
推荐文章:题目大解析3
进程是计算机系统中正在执行中的程序实例。它是操作系统对运行中程序的抽象表示。每个进程都有独立的内存空间和执行状态,可以包含一个或多个线程。
进程是计算机并发执行的基本单位。在多道程序设计环境中,操作系统通过调度算法将CPU时间片分配给不同的进程,使其交替执行,从而实现多个程序同时运行的效果。
每个进程具有以下特点:
独立性:每个进程拥有独立的地址空间和资源(如内存、文件、设备等),彼此之间相互隔离。这样可以确保进程之间的数据不会相互干扰,提高系统的安全性和稳定性。
并发性:操作系统通过调度算法,在有限的CPU资源上交替执行多个进程,使得它们看起来是同时进行的。通过并发执行,系统可以充分利用CPU的处理能力,提高整体的吞吐量。
资源管理:操作系统负责为进程分配和管理资源,包括内存空间、文件描述符、I/O设备等。进程可以申请和释放资源,通过操作系统提供的接口与外部环境进行数据交换。
线程支持:进程可以包含一个或多个线程,每个线程拥有独立的执行流和栈,但共享相同的内存空间和其他资源。线程之间的切换开销比进程小,可以更高效地实现并发执行和多任务处理。
进程间通信:不同进程之间可以通过进程间通信(IPC)机制进行数据交换和协作。常见的IPC方式包括管道(pipe)、共享内存(shared memory)、消息传递(message passing)等。
通过进程的概念,操作系统能够对运行中的程序进行管理和调度,实现多个程序同时运行、资源共享和数据交互,保证系统的稳定性和效率。
进程有两种的理解概念:
进程 = 可执行程序+内核数据结构(PCB)
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
按照课本概念,进程是一个运行起来的程序/在内存中的程序,那么程序在哪呢?
程序存放在磁盘文件中。
当程序被加载时,实际上就是可执行程序被加载拷贝到内存当中,那么接下来的一步就是cpu执行该程序吗?
不,我们在学操作系统管理时,有一句名言:先描述,再组织
所以,我们需要对该进程,也就是这个程序先进行描述,才能去执行。
所以此时我们引入了一个新概念————PCB(process control block)进程控制块
PCB中存放着对该进程的属性,也存放着下一个进程的节点地址,于是就可以将多进程联系在一起,而后就可以对进程进行增删查改
struct PCB1
{
//进程属性
struct* PCB2;
}
进程控制块(Process Control Block,PCB)是操作系统中用来描述和管理进程的数据结构。每个进程都对应一个唯一的PCB,PCB保存了进程的各种属性和状态信息。
PCB通常包含以下主要信息:
进程标识符(Process Identifier,PID):用于唯一标识每个进程的数字或字符串。
程序计数器(Program Counter,PC):记录当前进程正在执行的指令地址,即下一条将要执行的指令位置。
寄存器集合:保存进程的所有寄存器值,包括通用寄存器、程序状态字(PSW)、堆栈指针等。这些寄存器值在进程切换后可以恢复到原来的状态。
进程状态(Process State):表示进程当前所处的状态,如运行态(Running)、就绪态(Ready)、阻塞态(Blocked)等。操作系统通过改变进程的状态来实现进程的调度和切换。
程序和数据:保存进程的代码和数据,在内存中的位置信息。
进程优先级(Priority):用于调度算法确定进程获得CPU时间片的优先级。
打开文件表(File Descriptor Table):记录了进程打开的文件和相应的状态信息。
内存管理信息:包括进程的内存分配情况、页表信息等。
资源使用情况:记录了进程占用的各种系统资源,如内存、CPU时间、打开的文件数等。
PCB是操作系统管理和调度进程的重要数据结构。通过PCB,操作系统可以了解每个进程的状态、属性和所需资源,从而合理分配和管理系统资源。当操作系统进行进程切换时,PCB的信息将会被保存下来,以便下次恢复进程的执行状态。由于PCB保存了进程的全部信息,因此它也是进程在不同状态之间切换所必需的。
在操作系统中,多个进程需要通过CPU时间片轮转的方式分享CPU资源。为了有效地利用CPU资源,操作系统会根据所采用的调度算法,将就绪状态的进程排队等待CPU运行。这种状态下的进程被称为“排队进程”。
当一个进程处于就绪状态时,表示它已经准备好执行,但还没有分配到CPU资源。此时,操作系统会将该进程放到就绪队列里,等待CPU调度器选择并将其调度至CPU上执行。在就绪队列中,进程按照一定的优先级、时间片、先来先服务等规则排队等待。
当一个进程获得CPU时间片并开始执行后,它就不再是排队进程,而是“运行进程”。一旦进程的时间片用完或者它主动放弃CPU,它就会重新回到就绪状态,重新加入就绪队列中排队等待。
需要注意的是,由于操作系统可能同时存在多个就绪状态的进程,因此在进行进程调度时,需要考虑进程的优先级、等待时间、资源占用情况等因素,以确保系统的公平性和稳定性。
就像打开网易云音乐,播放音乐时它在运行队列,不播放则在内存中等待。
+在Linux中描述进程的结构体叫做task_struct
。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
在 Linux 操作系统中,进程控制块(Process Control Block,PCB)被称为任务结构(Task Structure)。它是描述和管理进程的数据结构,在内核中表示一个进程或线程的元信息。
Linux 的 PCB(Tasks Structure)包含了大量的信息,其中一些主要的字段包括:
进程标识符(Process Identifier,PID):用于唯一标识每个进程的数字或字符串。
进程状态(Process State):表示进程当前所处的状态,如运行态(Running)、就绪态(Ready)、睡眠态(Sleeping)、僵尸态(Zombie)等。
描述符表(Descriptor Table):记录了进程打开的文件、设备和网络连接等的相关信息。
内存管理信息:包括虚拟地址空间的布局、页表信息、堆栈指针等。
资源使用情况:记录了进程占用的各种系统资源,如内存、CPU时间、文件描述符等。
优先级和调度信息:包括进程的优先级、时间片大小、调度策略等。
程序计数器(Program Counter,PC):记录当前进程正在执行的指令地址,即下一条将要执行的指令位置。
寄存器集合:保存进程的所有寄存器值,包括通用寄存器、程序状态字(PSW)、堆栈指针等。
PCB 在 Linux 内核中起到了非常重要的作用,它包含了操作系统对进程进行调度、管理和切换所需的全部信息。通过访问和更新 PCB 中的字段,内核可以管理进程的状态转换、资源分配、调度切换等操作,从而实现多任务调度和并发执行。
在 Linux 操作系统中,ps
是一个常用的命令行工具,用于查看当前系统中运行的进程信息。ps
(process status)命令可以提供各种有关进程状态的信息。
以下是一些常用的ps
命令选项:
-e
:显示所有进程,包括其他用户的进程。-f
:显示完整的格式化输出,包括更详细的进程信息。-l
:以长格式显示进程信息,包括更多列和详细的信息(如进程的会话 ID、终端号等)。-u
:显示指定用户的进程信息。-p
:显示指定 PID 的进程信息。-a
:显示当前终端下所有进程信息,包括其他用户的进程。j
: 表示以作业控制格式显示输出,提供了更多的详细信息,包括进程的作业控制信息、会话 ID、终端号等。-x
:显示没有控制终端的进程信息(守护进程)。-o
:自定义输出格式,指定要显示的字段。--sort
:按指定的键对进程进行排序显示。--forest
:以树状结构显示进程之间的父子关系。-H
:显示进程的层次结构。这些选项可以组合使用,以便获得所需的进程信息。例如,ps -ef
可以显示所有进程的详细信息,ps aux
可以显示所有用户的进程信息,ps ajx
命令将显示所有进程的详细信息,格式为作业控制格式,并且包括没有控制终端的进程。
需要注意的是,ps
命令的选项在不同的操作系统中可能会有所区别,请根据实际情况查看对应系统的手册页(man page)获取更详细的信息。
在 Linux 系统中,/proc 目录是一个特殊的虚拟文件系统,用于提供关于当前正在运行的进程和系统状态的信息。它不是一个真正的文件系统,而是通过内核动态生成的一组文件和目录。
/proc 目录中的文件和子目录包含了有关系统和进程的各种信息,以下是一些常见的文件和目录:
/proc/[PID]:每个正在运行的进程都有一个以其进程ID(PID)命名的目录,其中包含与该进程相关的信息和属性。例如,/proc/1234 是进程ID为1234的进程所对应的目录。
/proc/cpuinfo:该文件提供了关于 CPU(处理器)的信息,如厂商、型号、核心数等。
/proc/meminfo:该文件提供了系统内存(RAM)的信息,包括总内存量、可用内存量、缓存等。
/proc/version:该文件包含有关内核版本的信息,如内核发布版本、编译日期等。
/proc/filesystems:该文件列出了当前系统支持的文件系统类型。
/proc/net:这是一个目录,包含了网络协议栈的信息,如 ARP、TCP、UDP 等。
/proc/sys:这是一个目录,包含了内核参数和系统配置的信息,可以通过修改其中的文件来改变系统行为。
除了上述示例之外,/proc 目录还包含其他文件和目录,提供了更多关于系统状态、硬件信息、文件系统、进程等的详细信息。
需要注意的是,/proc 目录中的文件都是虚拟的,其内容和结构在运行时动态生成,读取这些文件会提供实时的系统信息。同时,不应该直接修改 /proc 目录下的文件,因为这些文件是由内核控制的,并且任何错误修改可能会导致系统不稳定或出现问题。
进程id(PID)
父进程id(PPID)
获取
#include
#include //包含该头文件
#include
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
命令行中,父进程一般就是命令行解释器(bash)
在 Linux 中,Bash 是一种常见的 Unix shell(命令行解释器)和脚本语言,它是 Bourne Again Shell 的缩写。Bash 作为默认的交互式 shell,在大多数 Linux 发行版中都得到广泛使用。
Bash 提供了一个命令行界面,允许用户与操作系统进行交互。它解释用户输入的命令,并执行相应的操作。除了支持基本的命令执行外,Bash 还提供了丰富的功能,包括管道、重定向、通配符扩展、变量扩展、条件语句、循环语句等等,使得用户能够编写复杂的脚本来自动化任务和批处理操作。
Bash 脚本是由一系列命令和控制结构组成的文本文件,可以通过登录 shell 或直接解释器执行。脚本文件通常以 .sh
扩展名结尾。
Bash 具有与其他 Unix shell 相似的语法,如 Bourne Shell(sh)、Korn Shell(ksh)等,并且具有许多扩展和改进的功能。它是一个功能强大且灵活的工具,被广泛用于系统管理、脚本编写和开发等领域。
需要注意的是,虽然 Bash 在 Linux 系统中非常常见,但也可以在其他 Unix 类型的操作系统(如 macOS)和Windows 上使用。
unistd.h 是一个 C 语言的标准头文件,它包含了许多与操作系统交互和系统调用相关的函数和符号常量的声明。“unistd” 是 “UNIX standard” 的缩写,因此 unistd.h 通常在类 Unix 操作系统上使用,如 Linux、MacOS 等。
在 unistd.h 中定义了很多函数和符号常量,下面是其中一些常用的函数和符号常量:
函数:
access()
:检查文件的访问权限。chdir()
:改变当前的工作目录。close()
:关闭一个已打开的文件描述符。dup/dup2()
:复制文件描述符。execve()
:加载并执行一个新程序。fork()
:创建一个新进程。getpid()
:获取当前进程的进程ID。pipe()
:创建一个管道,实现进程间通信。read()/write()
:从文件或文件描述符中读取/写入数据。unlink()
:删除一个文件。usleep()
:延迟指定的微秒数。wait()/waitpid()
:等待子进程结束。符号常量:
STDIN_FILENO
、STDOUT_FILENO
、STDERR_FILENO
:表示标准输入、标准输出和标准错误的文件描述符。R_OK
、W_OK
、X_OK
、F_OK
,用于 access()
函数。unistd.h 头文件提供的函数和符号常量大多用于操作文件、进程控制、系统调用等底层操作。通过使用这些函数和常量,开发者可以编写更底层的系统程序或与操作系统进行更直接的交互。
在 Linux 中,kill
是一个用于终止进程的命令。它向指定的进程发送信号,从而影响进程的行为。
kill
命令的基本语法如下:
kill [options] ...
其中,
是要终止的进程的进程标识符(Process ID),可以指定一个或多个进程。
以下是一些常用的 kill
命令选项:
-l
:列出可用的信号名称。-s
:指定要发送的信号类型。可以使用信号名称或对应的数字编号。-
:与 -s
选项相同,用于指定要发送的信号类型。-p
:打印要发送信号的进程标识符,而不实际发送信号。--help
:显示帮助信息。一些常见的信号类型包括:
SIGTERM
(默认):请求进程正常终止。SIGKILL
:强制终止进程,进程无法捕获或阻止该信号。SIGINT
:从终端发送中断信号,通常由 Ctrl+C 触发。SIGHUP
:挂起信号,通常用于重新加载配置文件等操作。SIGSTOP
:停止进程的执行,进程会暂时被挂起。例如,要结束进程ID为 1234 的进程,可以使用以下命令:
kill 1234
如果需要强制终止一个进程,可以使用 -9
或 -SIGKILL
选项,如下所示:
kill -9 1234
需要注意的是,不慎终止某些关键进程可能会导致系统异常或数据丢失,因此在使用 kill
命令时请谨慎操作。
fork()
是一个在类 Unix 操作系统中的系统调用函数,用于创建一个新的进程。它会将当前进程(称为父进程)复制一份,形成一个新的进程(称为子进程)。这两个进程在执行相同的代码,但具有不同的进程ID。
fork()
函数的原型如下:
#include
pid_t fork(void);
其中,pid_t
是一个整数类型,表示进程ID。fork()
函数执行时会有以下几种情况:
fork()
返回子进程的进程ID,而子进程中 fork()
返回值为 0。fork()
返回一个负值,表示出现了错误。调用 fork()
后,操作系统将在内核中创建一个新的进程,并为其分配一个唯一的进程ID。新进程将继承父进程的大部分属性和资源,包括代码、数据、打开的文件描述符等。子进程从 fork()
函数返回后,可以继续执行代码,从调用位置开始。
由于父进程和子进程在 fork()
调用点之后完全独立运行,因此它们会同时执行代码。子进程是父进程的副本,但有自己独立的内存空间。这样,可以通过返回值来判断当前代码处于哪个进程中,以便执行特定的任务。通常,父进程会使用子进程的返回值来识别它的进程ID。
fork()
函数非常强大,它为多进程编程提供了基础。通过 fork()
,可以创建一个新的独立进程,使得程序能够同时执行不同的任务或并行处理。
fork之前,只有父进程执行相关代码,fork之后,父子进程都要执行后续的代码
fork函数以父进程的PCB/task_struct 为模板,在内存中创建子进程的PCB/task_struct,但问题是子进程没有它的代码,即可执行程序,所以,子进程共享父进程的代码。
fork函数共享父进程的代码,意味着fork前的代码,子进程也都能看到,但为什么子进程只执行fork之后的代码呢?
这是因为在父进程代码中,在汇编中有如pc/eip指令会存储下一条指令的地址,而子进程也继承了eip,
所以子进程执行fork之后的代码。
我们知道return也是代码。
当父进程和子进程被调度的时候,父子进程各要执行一段return 语句,所以fork会有两个返回值。
真实情况是:是一些寄存器存储的数值返回。
一个父亲可以有多个儿女,而一个人只有一个父亲。
所以子进程是多个的,给父进程要返回子进程的pid才知道具体是哪个儿女,而给子进程返回0就知道肯定是父进程,因为父进程只有一个。
当父子进程的PCB都被创建并在运行队列中排队时,用户不能确定到底是哪个进程先执行。
先后顺序由各自PCB中的调度信息(时间片
、优先级
)+调度器算法
决定,说人话就是由操作系统决定
我们这里需要知道一个结论:父子进程相对独立,删除任何一方进程不会影响另一个进程
这是因为操作系统会对父进程的代码进行写实拷贝出一个独立的代码块给子进程。
而写实拷贝分为:
但父子进程对代码块不进行写入修改时,用的是浅拷贝。
但父子进程要对代码块进行写入修改时,用的是深拷贝进行代码同步。
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注❤️ ,学海无涯苦作舟,愿与君一起共勉成长