Q1:什么是程序,什么是进程,有什么区别?
程序: 程序是一种静态的实体,它是计算机中一组有序的指令集合,这些指令定义了完成特定任务所需的操作序列。程序通常以某种编程语言编写,经过编译或解释后转换为机器语言,使得计算机能够理解并执行。程序包含了算法、逻辑、变量、函数、类等结构,以及相关的数据和资源引用。程序是逻辑上的概念,存储在磁盘或其他非易失性存储介质中,具有持久性,即使在没有被执行时也依然存在。程序本身不具备运行的含义,不占用系统资源,也不参与任何实际的计算活动。
进程: 进程是程序在计算机系统中的一次具体执行过程,是一个动态的实体。当操作系统加载并运行程序时,会为其创建一个进程。进程包含了执行程序所需的全部信息和资源,包括程序代码、相关数据、堆栈、内存空间、打开的文件、设备句柄、处理器状态、优先级、安全属性等。进程具有以下特点:
- 动态性:进程是程序的实际运行表现,涉及到CPU调度、内存分配、输入输出操作等动态行为,具有生命周期,从创建、运行、暂停、恢复到最终终止。
- 并发性:操作系统可以同时管理多个进程,它们可以在多核处理器上并发执行,或者通过时间片轮转等方式在单个处理器上交替执行,从而实现多任务处理。
- 独立性:每个进程拥有自己的虚拟地址空间,与其他进程隔离,确保数据的安全性和资源的隔离性。进程间可以通过进程间通信机制(如管道、消息队列、共享内存等)进行协作或数据交换。
- 异步性:进程的执行并非严格顺序进行,它们可能因为等待IO操作、系统调用、时间片用完等原因被暂停,然后在满足条件后被重新调度执行。
程序与进程的区别:
静态与动态:
- 程序是静态的,它只是指令的集合,不涉及具体的运行状态和执行过程。
- 进程是动态的,它是程序的实例化,包含了程序的执行上下文,涉及到内存分配、指令执行、系统资源调度等实际运行情况。
存在形式与生命周期:
- 程序作为磁盘上的文件存在,具有永久性,只要文件未被删除,程序就一直存在。
- 进程作为操作系统中正在运行的实体存在,具有临时性,有明确的生命周期,包括创建、运行、暂停、恢复和终止等阶段。
资源占用与系统认可:
- 程序自身并不直接占用系统资源(如CPU、内存),仅在磁盘上占用存储空间。
- 进程在执行过程中占有各种系统资源,如内存、文件、设备等,并且是操作系统进行资源调度和管理的基本单位,只有建立了进程的程序才能得到操作系统的认可和支持,得以真正执行。
并发与独立性:
- 程序本身不体现并发性,同一程序可以被多次加载并创建为多个不同的进程,实现并发执行。
- 进程具有独立的地址空间和系统资源,可以独立运行,也可以通过同步或通信机制与其他进程协同工作。
总结来说,程序是完成特定任务的指令集,是软件的静态表现形式;而进程则是程序在计算机系统中的一次具体执行过程,是操作系统进行资源分配、调度和管理的基本单位,具有动态性、并发性、独立性和生命周期等特征。
Q2:如何查看系统中有哪些进程?
在Linux系统中,可以使用多种命令来查看当前系统中正在运行的进程。以下是几种常用的查看进程的方法:
1.
ps
命令
ps
命令用于报告当前系统的进程状态。基础用法如下:bash
ps
这将显示当前终端下属于当前用户的进程。为了查看更全面的系统进程信息,可以使用以下选项:
bash
ps aux
参数解释:
a
:显示所有用户的进程,不仅限于当前用户。u
:以用户为主的格式显示进程信息,包含用户名、CPU和内存使用百分比等。x
:显示没有控制终端的进程(即后台进程)。2.
top
命令
top
命令提供了一个交互式的实时视图,动态显示系统中各个进程的资源使用情况,包括CPU、内存、运行时间等。只需在终端中输入:bash
top
在
top
界面中,你可以看到实时更新的进程列表,按CPU或内存使用量排序,还可以进行搜索、排序、过滤以及对进程进行操作(如更改优先级、杀死进程等)。3.
pgrep
命令如果你想要根据进程名称或部分名称来查找进程,可以使用
pgrep
命令。例如:bash
pgrep -l <process_name>
参数解释:
-l
:显示匹配进程的完整命令行。这将列出所有与
<process_name>
匹配的进程及其PID。4.
pstree
命令
pstree
命令以树状结构展示进程间的派生关系,有助于理解进程间的父子关系:bash
pstree
如果想查看某个特定进程及其子进程,可以加上进程ID:
bash
pstree -p <pid>
参数解释:
-p
:显示进程ID。5.
/proc
目录Linux系统中的
/proc
目录提供了内核及进程的详细信息。每个进程都有对应的目录/proc/<pid>
,其中<pid>
是进程ID。进入这个目录,可以看到该进程的各种详细文件,如cmdline
(命令行参数)、status
(状态信息)、stat
(简略状态)、io
(输入输出统计)等。浏览这些文件可以获取进程的详细信息。例如,查看进程ID为
1234
的进程的命令行:bash
cat /proc/1234/cmdline
6.
htop
命令(可能需要安装)
htop
是一个增强版的top
工具,提供了彩色界面、搜索、过滤、树状视图等功能,更加用户友好。如果尚未安装,可以通过包管理器(如apt
、yum
或dnf
)进行安装。使用方法与top
类似:bash
htop
通过上述命令,你可以根据需要选择合适的方式来查看Linux系统中当前运行的进程
Q3:什么是进程标识符?
在Linux系统中,进程标识符(Process Identifier, PID)是用来唯一标识系统中每一个进程的整数。每个运行中的进程都有一个与之关联的非负整数PID,它是由操作系统内核在进程创建时自动分配的。PID的主要作用在于让系统和用户能够区分不同的进程,并对它们进行管理和监控。
PID的作用和特性:
唯一性:在同一时刻,系统中任何两个进程的PID都是不同的。当一个进程结束时,其PID可能会被系统重新分配给新创建的进程,但同一时间不会有重复的PID。
进程管理:通过PID,操作系统可以跟踪、调度、控制和通信各个进程。用户和管理员也可以使用PID来对特定进程执行操作,如查看进程状态、更改进程优先级、发送信号、或者终止进程等。许多系统命令(如
kill
、renice
、ps
、top
等)都需要指定目标进程的PID来执行相应的操作。进程关系:PID还用于表示进程间的父子关系。当一个进程通过
fork()
系统调用创建新进程时,子进程会继承父进程的某些属性,并获得一个新的PID。父进程可以通过wait()
或waitpid()
系统调用来等待子进程结束,并获取其退出状态。此外,进程的PID可以通过getppid()
函数获取其父进程的PID。PID范围:在Linux系统中,PID通常是32位整数,其取值范围理论上为0至
2^32-1
。然而,为了防止PID循环使用导致的问题,系统通常会保留一部分PID空间,如避免使用0、1和低值PID(通常用于特殊进程,如init进程通常为1),以及避免使用已经被系统保留或最近使用过的PID。因此,实际可用的PID范围通常小于理论最大值。PID重用与PID wraparound:随着进程的创建和销毁,PID会被反复使用。当系统达到最大可用PID时,PID计数器会回绕到较低的未使用的值,这一现象称为PID wraparound。为了避免因PID重用导致的问题,现代Linux内核引入了“PID namespaces”功能,允许每个命名空间内的PID独立计数,减少全局PID耗尽的风险,并增强了容器等隔离环境的安全性。
综上所述,进程标识符(PID)是Linux系统中识别和管理进程的核心元素,它确保了对系统中众多并发执行的进程进行准确、唯一的标识和控制。
Q4:什么叫父进程,什么叫子进程?
在Linux(以及其他类Unix系统)中,进程间的创建关系形成了父子进程的概念:
父进程: 父进程是指创建另一个进程的进程。当一个进程通过调用
fork()
系统调用时,操作系统会创建一个与原进程几乎完全相同的副本,这个新创建的进程就是子进程,而发起fork()
调用的原进程则成为子进程的父进程。父进程与子进程共享相同的程序代码,但各自拥有独立的进程控制块(PCB,包含进程的状态、资源等信息)、内存空间(包括堆、栈、数据段等)以及系统资源(如打开的文件描述符、信号处理函数等)。父进程可以继续执行自己的流程,而子进程则从fork()
调用处开始执行,两者并行执行,互不影响。子进程: 子进程是由其他进程(父进程)通过系统调用(通常是
fork()
)创建的新进程。子进程在创建时继承了父进程的许多属性,包括但不限于:
- 进程标识符(PID):子进程获得一个新的PID。
- 环境变量:子进程继承父进程的环境变量副本。
- 当前工作目录:子进程继承父进程的工作目录。
- 文件描述符:子进程继承父进程打开的所有文件描述符的副本,这些描述符指向相同的文件,共享相同的读写偏移量。
- 进程组和会话:子进程加入到父进程所在的进程组和会话中。
- 信号处理设置:子进程继承父进程的信号处理函数和阻塞信号集。
子进程虽然继承了父进程的诸多属性,但它是一个独立的执行实体,有自己的内存空间、程序计数器和寄存器状态,可以执行不同的代码路径,修改自己的数据,打开或关闭文件,甚至创建自己的子进程。
父子进程关系的影响:
- 资源清理:当子进程结束后,其退出状态(返回值)可以通过
wait()
或waitpid()
系统调用由父进程获取。父进程负责回收子进程占用的资源,如释放子进程的PCB、关闭子进程打开的文件描述符等。如果父进程不等待子进程结束(即不调用wait()
系列函数),子进程将成为“僵尸进程”,占用少量系统资源,直到其父进程被终止或显式地等待它。- 孤儿进程:如果父进程先于子进程结束且没有等待子进程,子进程会变为“孤儿进程”。在这种情况下,操作系统会将子进程的父进程设置为
init
进程(PID通常为1),由init
进程负责后续的资源清理工作,避免资源泄漏。- 信号传递:某些信号(如
SIGINT
、SIGQUIT
、SIGTERM
等)默认会向进程组内的所有进程发送。子进程通常属于父进程所在的进程组,因此这些信号会影响整个进程组。总之,在Linux中,父进程和子进程是通过
fork()
系统调用创建出来的、具有继承关系的两个独立执行实体。它们各自拥有独立的资源和状态,但子进程初始时继承了父进程的诸多属性,并在生命周期中保持着一定的交互和影响。
Q5:C程序的存储空间是如何分配?
C程序在运行时的存储空间分配主要遵循以下几大区域划分:
代码段(Code Segment):
- 用于存储程序的机器指令。编译后的可执行文件中,所有函数的机器代码都位于此区域。代码段通常是只读的,以防止程序意外修改自身的指令。操作系统在程序加载时将其载入内存,并确保同一程序的所有进程实例共享同一份代码段,以节省内存资源。
全局/静态数据区(Data Segment):
- 存放全局变量和静态变量(包括文件作用域和函数作用域的静态变量)。这部分空间进一步细分为已初始化数据段和未初始化数据段(BSS段):
- 已初始化数据段(Initialized Data Segment): 存储在程序编译时已赋初值的全局变量和静态变量。这些变量在程序加载时由操作系统直接从可执行文件中复制到内存中。
- BSS段(Block Started by Symbol): 存储未初始化或初始化为零的全局变量和静态变量。BSS段并不在可执行文件中占据实际空间,而是由操作系统在程序加载时动态分配并初始化为零。这样做既节省了磁盘空间,又提高了内存使用效率。
堆(Heap):
- 动态内存区域,用于程序在运行时通过函数如
malloc()
,calloc()
,realloc()
等动态分配的空间。堆内存从高地址向低地址增长,其大小不固定,可以根据程序的需要动态扩张或收缩。程序员负责通过相应的内存管理函数申请和释放堆内存,如果不正确地使用会导致内存泄漏。栈(Stack):
- 用于存放函数调用时的局部变量、函数参数、返回地址以及一些与函数调用相关的状态信息(如寄存器保存等)。栈内存从低地址向高地址增长,其大小在程序启动时由操作系统设定,通常有一定的限制(可通过 ulimit 命令查看)。每当函数被调用时,系统会在栈顶为其分配空间;函数返回时,这部分空间被自动释放。栈空间的分配和回收由编译器和运行时环境自动管理,无需程序员显式干预。
程序堆栈(Text Stack):
- 在某些文档中提到的“程序堆栈”实际上是指上述的“栈(Stack)”,用于存放函数调用时的局部变量等信息。
常量区(Constant Data Segment):
- 存放字符串字面量、整型或浮点型常量等在编译时就能确定且不可更改的数据。这部分内存也是只读的,以防止程序意外修改常量值。常量区的内容通常与代码段一起被加载到内存中。
总结起来,C程序的存储空间分配主要包括代码段存放指令、全局/静态数据区存放全局和静态变量、堆用于动态内存分配、栈用于函数调用时的局部数据存储,以及常量区存放不可变的常量数据。这些区域各有特点,分别服务于程序的不同需求,并由操作系统和编译器在程序运行时进行管理和维护。