操作系统面试高频(进程)
进程
1 进程的定义⭐⭐⭐⭐⭐
进程是指在计算机中执行的一个程序。每个进程都有自己的内存空间和系统资源,如文件、端口、线程等。进程是计算机操作系统中的一个基本概念,是操作系统进行资源分配和调度的最小单位。每个进程都有一个唯一的标识符(进程号),可以在操作系统中进行管理和控制。
在操作系统中,每个进程都有自己的地址空间和指令和数据,这个地址空间通常由内核进行分配和管理。在运行时,进程可以创建和销毁多个线程,每个线程可以在进程的地址空间中独立运行。
每个进程可分为以下几个部分:
1 程序代码区:包含执行程序的指令代码
2 数据区:包含程序执行时所需的数据
3 堆:动态分配的内存空间
4 栈:运行时函数调用、返回和局部变量存储的空间
操作系统通过进程控制块(Process Control Block,PCB)来记录和管理每个进程的状态、所有权等信息,每个进程的PCB包含进程标识符、进程状态、CPU寄存器状态、内存分配状态、打开文件状态信息等。操作系统可以通过PCB来进行进程的调度和管理,以保证计算机系统的高效运行。
2 进程的特征⭐⭐⭐⭐⭐
进程具有以下特征:
1 动态性:进程是动态产生和消失的,系统中可以同时存在多个进程,新的进程可以随时被创建,而已有的进程也可以随时被销毁。
2 独立性:每个进程都有自己的内存空间和系统资源,进程之间不会相互干扰。
3 并发性:多个进程可以同时运行,计算机系统可以快速地进行多任务处理,提高效率,有利于实现多人共享系统资源。
4 异步性:不同进程的执行速度不同,各个进程之间的执行顺序和时间不受约束,执行顺序和速度都是不可预知的。
5 独立的地址空间:每个进程有自己的地址空间,进程之间的数据是相互隔离的,提高了计算机系统的安全性。
6 同步与通信:不同的进程之间可以进行通信,可以利用操作系统提供的信号量、共享内存、管道等机制来实现进程间的同步和通信。
7 共享与互斥:在进程并发执行时,可能会有多个进程要对同一资源进行访问,通过锁机制实现资源的互斥访问,避免了资源冲突和死锁问题。
3 进程的五种基本状态及转换⭐⭐⭐⭐⭐
进程有五种基本状态:创建状态、就绪状态、运行状态、阻塞状态和终止状态。
1 创建状态:当操作系统接受到用户进程创建的请求时,会为进程分配资源,并将进程的PCB加入到系统的进程队列中。
2 就绪状态:当进程具备运行条件时,它被加入就绪队列中,等待操作系统为其分配CPU执行时间。
3 运行状态:当进程获得CPU执行权后,进程开始运行,执行其指令,直到结束或者进入阻塞状态。
4 阻塞状态:当进程在运行时需要等待其它的事件发生,比如等待I/O的完成,或者等待系统调用的结果,进程会从运行状态转换到阻塞状态。
5 终止状态:当进程完成其任务或者因为异常情况终止时,进程会从任何状态转移到终止状态,回收并释放系统资源。
进程状态之间的转换如下:
1 从创建状态到就绪状态:系统为进程分配资源后,进程进入就绪状态,等待系统分配CPU执行时间。
2 从就绪状态到运行状态:当进程获得CPU执行资格时,进程进入运行状态,开始执行。
3 从运行状态到就绪状态:当进程执行完其指令后, 或者因为时间片用完而被中断时,进程从运行状态转换到就绪状态。
4 从运行状态到阻塞状态:当一个进程在执行中需要等待其它事件的发生,比如等待I/O完成时,进程会从运行状态转换为阻塞状态,等待事件完成。
5 从阻塞状态到就绪状态:当一个进程等待的事件完成时,它就转移到就绪状态,等待系统重新为其分配CPU执行时间。
6 从运行状态到终止状态:当进程执行完成或者因为异常情况终止时,它就转移到终止状态,系统回收并释放该进程所占有的资源。
4 创建进程的方式?⭐⭐⭐⭐⭐
在 Linux 系统中,创建进程主要有以下几种方式:
使用 fork()
函数
- 原理:
fork()
是创建新进程的基础系统调用。调用fork()
时,操作系统会复制当前进程(父进程),生成一个几乎完全相同的子进程。子进程会获得父进程数据空间、堆和栈的副本,且与父进程共享代码段。 - 返回值:在父进程中,
fork()
返回子进程的进程 ID(一个正整数);在子进程中,返回 0;若出错则返回 -1。通过返回值,父进程和子进程可区分彼此并执行不同的代码逻辑。 - 示例代码:
#include <stdio.h> #include <unistd.h> int main() { pid_t pid = fork(); if (pid < 0) { perror("fork"); return 1; } else if (pid == 0) { // 子进程代码 printf("This is the child process, pid = %d\n", getpid()); } else { // 父进程代码 printf("This is the parent process, child pid = %d\n", pid); } return 0; }
fork()
与 exec()
系列函数配合
- 原理:
fork()
创建子进程后,子进程可调用exec()
系列函数来执行新的程序。调用exec()
时,子进程的内存空间会被新程序替换,开始执行新程序的代码。 - 常见函数:包括
execl()
、execv()
、execle()
、execve()
、execlp()
、execvp()
等,这些函数在参数传递方式和查找可执行文件的路径上存在差异。 - 示例代码:
#include <stdio.h> #include <unistd.h> int main() { pid_t pid = fork(); if (pid < 0) { perror("fork"); return 1; } else if (pid == 0) { // 子进程执行新程序 if (execl("/bin/ls", "ls", "-l", NULL) == -1) { perror("execl"); return 1; } } else { // 父进程等待子进程结束 wait(NULL); printf("Child process finished.\n"); } return 0; }
使用 vfork()
函数
- 原理:
vfork()
也是用于创建新进程的系统调用,与fork()
类似,但vfork()
创建的子进程会共享父进程的地址空间,直到子进程调用exec()
系列函数或_exit()
退出。 - 特点:
vfork()
保证子进程先运行,在它调用exec()
或_exit()
之后父进程才可能被调度运行。 - 示例代码:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid = vfork(); if (pid < 0) { perror("vfork"); return 1; } else if (pid == 0) { // 子进程代码 printf("This is the child process, pid = %d\n", getpid()); _exit(0); } else { // 父进程代码 printf("This is the parent process, child pid = %d\n", pid); } return 0; }
使用 system()
函数
- 原理:
system()
函数可在程序中执行一个 shell 命令。它内部会调用fork()
创建子进程,然后在子进程中调用exec()
来执行指定的 shell 命令。 - 优点:使用简单,适合执行一些简单的系统命令。
- 示例代码:
#include <stdio.h> #include <stdlib.h> int main() { int status = system("ls -l"); if (status == -1) { perror("system"); return 1; } return 0; }
5 什么时候用进程?⭐⭐⭐⭐⭐
在操作系统中,进程是资源分配和调度的基本单位。以下是适合使用进程的常见场景及简介:
1. 需要资源隔离时
- 场景:多个任务需要独立的内存空间、文件描述符等资源,避免相互干扰。
- 例子:浏览器中每个标签页作为独立进程,一个页面崩溃不影响其他页面。
2. 并行处理任务
- 场景:需要充分利用多核 CPU,或处理 CPU 密集型任务(如视频渲染、科学计算)。
- 特点:进程间通过消息传递(如管道、Socket)通信,适合无共享数据的并行任务。
3. 运行独立程序
- 场景:通过
exec()
系列函数启动外部程序(如 Shell 命令、第三方工具)。 - 例子:在 C 程序中调用
system("ls -l")
执行文件列表命令。
4. 需要高可靠性和容错性
- 场景:某个任务崩溃时,不影响其他任务的运行。
- 例子:Web 服务器的多进程模型,单个进程故障后可自动重启。
5. 守护进程与服务程序
- 场景:长期运行的后台服务(如数据库、Web 服务器)。
- 特点:独立于终端,可在系统启动时自动运行。
与线程的对比
- 进程:资源隔离性强,适合任务独立性高的场景,但创建开销大。
- 线程:轻量级,共享进程资源,适合任务协作频繁的场景(如 I/O 密集型服务)。
总结
选择进程的核心原则是任务独立性和资源隔离。当任务需要独立运行、避免相互影响,或需要充分利用多核性能时,进程是更合适的选择。
6 进程控制⭐⭐⭐⭐
进程控制指的是操作系统通过对进程的调度、调度策略、进程优先级等方式来控制进程的执行。进程控制由操作系统进行,是操作系统对进程状态的监控和管理。具体的控制方式包括以下几种:
1 进程调度:指操作系统在多个就绪进程中选取一个进程分配CPU执行时间的过程。进程调度的目的是提高CPU的利用率和系统的响应速度。
2 进程同步:多个进程之间为了协作完成某一任务,必须要进行同步。操作系统提供了各种机制,如信号量、互斥锁、条件变量等来实现进程间的同步。
3 进程通信:进程通信是指多个进程之间交换信息和共享数据的过程。进程通信是通过操作系统提供的IPC机制来实现的,如管道、消息队列、共享内存、Socket等。
4 进程安全:进程安全指保证进程能够正确、合法的执行,并防止进程间相互干扰和冲突的一组机制。进程安全是操作系统对进程进行的重要控制之一,其技术手段包括:鉴权、进程隔离、进程权限控制等。
5 死锁处理:在操作系统中,进程之间可能会因为资源的竞争和协作等原因产生死锁。操作系统提供了死锁预防和死锁解除的机制,如银行家算法、超时机制、资源剥夺等方式来避免和解除死锁。
7 进程同步方式⭐⭐⭐⭐⭐
在多进程操作系统中,由于多个进程都需要共享系统资源,同时对这些资源进行访问可能会产生冲突,因此需要进行同步与互斥。常见的进程同步方式包括:
信号量: 信号量是一个整形变量,可以用来控制多个进程对共享资源的访问。在信号量机制中,对共享资源进行互斥访问的进程会对信号量进行P(proberen)操作,而释放共享资源的进程则进行V(verhogen)操作。当信号量为1时,表示共享资源可以被访问,为0时表示已经被占用。
互斥锁:互斥锁是一种二元信号量,只有0和1两种状态,可以用来保证多个进程互斥地访问共享资源。只有获取到锁的进程才能访问共享资源,其他进程需要等待锁的释放。当锁被占用时,其他进程对锁的请求会被阻塞,直到锁被释放。
条件变量:条件变量可以用来实现进程间的同步和通信。条件变量通常与互斥锁一起使用,当共享资源的某个条件未得到满足时,会阻塞等待,当获取到满足条件的资源时,可以通过条件变量通知阻塞的进程获取共享资源。
读写锁: 读写锁是一种特殊的锁,用于在数据读取操作多于写入操作的情况下提高并发性能。具体来说,读写锁允许多个读者访问,但只允许一个写者访问。当写操作正在被执行时,所有读和写操作都将被阻塞。
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
该专栏面向嵌入式开发工程师、C++开发工程师,包括C语言、C++,操作系统,ARM架构、RTOS、Linux基础、Linux驱动、Linux系统移植、计算机网络、数据结构与算法、数电基础、模电基础、5篇面试题目、HR面试常见问题汇总和嵌入式面试简历模板等文章。超全的嵌入式软件工程师面试题目和高频知识点总结! 另外,专栏分为两个部分,大家可以各取所好,为了有更好的阅读体验,后面会持续更新!!!