(嵌入式八股)第5章 操作系统(一)

5.1 RTOS与Linux的区别

RTOS(实时操作系统)

定义:RTOS 是专门设计来满足实时计算需求的操作系统。它的核心目标是确保任务能够在严格的时间约束下完成,通常用于要求严格时间响应的系统中。

特点

  • 实时性:RTOS 具有严格的时间控制,能够在规定的时间内完成任务,保证任务按时执行。它可以保证高优先级的任务在指定时间内得到处理,这对于工业控制、汽车安全等应用至关重要。
  • 确定性:RTOS 提供确定性响应,即任务在任何情况下都能在预定时间内响应。它的行为是可预测的。
  • 优先级调度:RTOS 通常采用优先级调度,确保高优先级的任务优先执行。
  • 低延迟:RTOS 对任务的调度延迟非常小,能够确保及时响应外部事件。

应用场景

  • 工业控制:比如自动化生产线,设备控制系统。
  • 嵌入式系统:如汽车电子、航天控制、医疗设备等,要求实时响应。
  • 航空航天:飞行控制系统、航天器的实时计算需求。

例子

  • FreeRTOS
  • VxWorks
  • RTEMS
  • QNX

Linux(类Unix操作系统)

定义:Linux 是一种开源的类 Unix 操作系统,它的设计目标并非实时性,而是为广泛的计算机系统提供通用的操作系统功能,包括桌面计算、服务器、嵌入式设备等。

特点

  • 通用性:Linux 适用于从桌面计算机到服务器,再到嵌入式设备等各种平台。
  • 多任务处理:Linux 是多任务、多用户操作系统,支持广泛的软件和硬件。
  • 延迟不可预测:尽管 Linux 提供了较好的多任务处理能力,但它的响应时间可能不够确定,因此它不适用于严格要求的实时任务。
  • 调度:Linux 采用基于时间片的调度方法,通常任务的优先级处理不是实时的。虽然 Linux 支持实时扩展(如实时内核补丁 PREEMPT-RT),它并不自带严格的实时能力。

应用场景

  • 桌面操作系统:如 Ubuntu、Fedora、Debian 等。
  • 服务器:Linux 是全球最广泛使用的服务器操作系统。
  • 嵌入式系统:虽然 Linux 可以用于嵌入式系统,但它不适用于所有需要硬实时要求的场景。

例子

  • Ubuntu
  • CentOS
  • Debian
  • Android(基于 Linux 的操作系统)

RTOS 与 Linux 的区别

总结

  • RTOS 是为需要严格时间控制和高确定性任务的应用而设计的操作系统,通常应用在工业控制、汽车电子、航空航天等需要硬实时保障的领域。
  • Linux 是一种通用的操作系统,主要应用于桌面计算、服务器和一些嵌入式系统中。它适用于大多数不需要严格实时性的场景,尽管通过修改内核可以在一定程度上提供实时支持(如PREEMPT-RT补丁)。

所以,RTOSLinux主要区别在于实时性的要求,RTOS 更加注重时间的精确控制,而 Linux 则侧重于广泛的通用性和灵活性。

5.2 冯诺依曼结构和哈佛结构的区别

冯诺依曼结构(Von Neumann Architecture)和哈佛结构(Harvard Architecture)是计算机体系结构的两种主要模型。它们的主要区别在于存储和处理数据的方式。

冯诺依曼结构(Von Neumann Architecture)

定义:冯诺依曼结构是一种计算机体系结构模型,其中数据和指令都存储在同一个内存空间中,使用同一条总线来传输数据和指令。

特点

  • 单一存储器:数据和程序指令都存储在相同的内存中。
  • 共享总线:计算机使用一条总线传输指令和数据。这样,当CPU取指令时,需要等到指令加载到CPU之后,才能进行数据的传输。
  • 处理速度受限:由于数据和指令共用总线,导致“冯诺依曼瓶颈”,即CPU需要在指令和数据之间不断切换,限制了系统的处理速度。

优点

  • 设计简单,成本较低,适用于一般的计算机和通用计算任务。
  • 程序和数据存储在同一内存中,程序可以动态地进行修改和加载。

缺点

  • 由于数据和指令共享同一个内存空间,可能导致“冯诺依曼瓶颈”,即指令获取速度受限。
  • 执行速度受到总线带宽的限制。

哈佛结构(Harvard Architecture)

定义:哈佛结构是一种计算机体系结构模型,其中数据存储和指令存储分别位于不同的内存区域,并使用不同的总线进行传输。也就是说,指令和数据在硬件级别上是分开的。

特点

  • 分离存储器:数据和程序指令被存储在不同的存储器中。指令存储和数据存储各自拥有独立的内存和总线。
  • 并行访问:由于指令和数据使用不同的总线,CPU可以同时访问指令和数据,从而提高了处理效率。
  • 高效执行:哈佛结构可以并行执行数据读取和指令读取,减少了“瓶颈”问题,提高了系统的执行速度。

优点

  • 提高了执行速度,尤其适合需要高性能处理的任务(如嵌入式系统和数字信号处理)。
  • 分离存储器使得指令和数据可以同时访问,减少了等待时间。

缺点

  • 设计较为复杂,成本较高,特别是在需要两条独立总线的情况下。
  • 不如冯诺依曼结构灵活,程序存储和数据存储分开,灵活性较低。

冯诺依曼结构 vs 哈佛结构的区别

总结

  • 冯诺依曼结构适用于通用计算任务,设计简单,但可能遇到“冯诺依曼瓶颈”限制其性能。
  • 哈佛结构适用于对性能要求较高的场景,尤其是在嵌入式系统和数字信号处理领域,能实现更高效的并行访问,但设计较为复杂。

5.3 CPU 工作原理

CPU(中央处理器)是计算机的核心部件,它负责执行程序中的指令并进行计算、控制等操作。CPU 的工作过程通常分为几个步骤,下面我们详细介绍这几个步骤的执行过程。

取指令(Instruction Fetch)

  • 描述:这是 CPU 执行程序的第一步。CPU 从内存中获取当前要执行的指令。根据程序计数器(PC,Program Counter)中的地址,CPU 会从内存中读取指令。
  • 执行过程:程序计数器(PC)保存下一条指令的地址,CPU 会使用这个地址去内存中提取指令并将其加载到指令寄存器中。指令缓存(Instruction Cache)可能被用来提高取指速度,减少访问主内存的延迟。

解码指令(Instruction Decode)

  • 描述:取指令后,CPU 需要解析这条指令。解码阶段,CPU 会将指令分解成若干个字段,分析出操作码(Opcode)和操作数(Operands),以确定将要执行的具体操作。
  • 执行过程:指令解码器会解析操作码,确定操作类型(如算术运算、数据加载、存储、分支跳转等)以及操作数(可能是寄存器、内存地址或常量等)。解码过程有助于后续的执行阶段。

执行指令(Execute)

  • 描述:在这一阶段,CPU 根据解码后的信息执行指令中指定的操作。这可能涉及算术运算、逻辑运算、地址计算、条件判断等。
  • 执行过程:例如,如果是加法指令,CPU 会调用算术逻辑单元(ALU)执行加法操作。如果是跳转指令,CPU 会计算跳转地址并修改程序计数器(PC)的值。此时,CPU 还可能计算内存地址,为后续的内存访问做准备。

访问内存(Memory Access)

  • 描述:如果指令需要访问内存(例如读取数据或写入数据),这一步会发生。读取或写入操作可能需要使用地址寄存器(如内存地址寄存器 MAR)。
  • 执行过程:CPU 根据指令要求从内存中读取数据或将数据写入内存。此时会使用 ALU 或其他硬件单元来进行地址计算,完成对指定内存位置的访问。访问内存的速度通常较慢,因此现代 CPU 使用缓存(如数据缓存、指令缓存)来加速这一过程。

写回结果(Write Back)

  • 描述:这是指令执行过程的最后一步,如果指令产生了结果(例如运算结果),CPU 会将结果写回到寄存器或内存中,以供后续指令使用。
  • 执行过程:例如在算术运算后,结果将被写入寄存器(如累加器)。如果是存储指令,结果将被写入指定的内存地址。写回阶段使得结果能够在后续的指令中继续使用。

总结

CPU 的工作过程是一个循环不断进行的过程,每个周期中都包含了取指令、解码、执行、访问内存和写回结果五个关键步骤。这个过程使得计算机能够按照预定的顺序执行程序,从而完成各种计算任务。理解 CPU 的工作原理有助于优化程序设计和提升计算机系统的效率。

5.4 中断发生的过程和处理流程

中断是一种机制,允许计算机系统在特定事件或条件发生时暂停当前的执行,并处理这些事件。中断的作用是允许系统在发生外部事件时迅速响应,保证系统对外部设备和环境的响应能力。以下是中断发生和处理的基本流程。

硬件触发中断

  • 描述:中断通常由硬件设备触发,如时钟中断、外部设备发出的信号(例如I/O设备)或异常情况(如除零错误、内存访问错误等)。硬件设备通过向中断控制器发送中断请求信号来发出中断。
  • 触发方式:外部中断:例如外部设备(如键盘、鼠标、硬盘、网络接口等)产生的中断。内部中断:如程序执行时发生的异常(如除零错误)。

中断请求处理

  • 描述:当硬件设备发出中断请求信号时,CPU会检查中断请求线。如果有中断请求,CPU会暂停当前执行的任务并响应中断。
  • 执行过程:中断控制器会将中断请求传递给CPU,CPU会根据优先级判断是否响应中断。如果中断优先级较高,CPU会暂停当前执行的程序并处理中断。

中断处理程序调用

  • 描述:中断发生后,CPU会根据中断号或中断向量表中的映射关系找到相应的中断处理程序的入口地址。中断向量表是一个存储中断类型和对应处理程序入口地址的表。
  • 执行过程:CPU会保存当前执行程序的状态(如程序计数器、寄存器等)到堆栈或其他保存区域,以便中断处理完成后能够恢复原程序的执行。CPU会根据中断号跳转到对应的中断处理程序。

中断处理

  • 描述:中断处理程序执行中断事件的处理逻辑,处理程序根据不同的中断类型(硬件中断、软件中断、外部事件等)执行相应的任务。
  • 执行内容:保存寄存器状态:避免中断处理时修改当前程序的运行状态。处理事件:例如响应I/O设备请求、时钟中断、数据处理等。更新数据结构:比如调整硬件设备的状态、数据缓冲区的读写操作等。

恢复执行

  • 描述:中断处理完毕后,CPU会恢复到被中断的程序执行状态。中断处理程序通常会通过之前保存的状态信息恢复被中断程序的执行环境。
  • 执行过程:从堆栈或其他保存的寄存器中恢复中断前的程序状态。恢复后,CPU继续执行原来的程序,仿佛没有发生中断一样。

解释和示例

  • 例子:假设有一个外部设备(如硬盘)正在向系统发送数据。当硬盘准备好数据时,它通过中断信号通知CPU进行数据处理。CPU响应硬件的中断信号,暂停当前程序的执行,执行中断处理程序来处理硬盘数据。处理完后,CPU恢复执行之前的程序。

总结

中断机制使得计算机能够对外部事件做出响应,提高了计算机的实时性和响应能力。它通过硬件设备发出的请求信号、优先级调度、栈保存、恢复执行等步骤来确保程序在中断时能够顺利处理,并且返回继续执行。

5.5 中断上下文的概念和特点

中断上下文是指当处理器正在执行中断服务例程(ISR, Interrupt Service Routine)时,系统的运行状态。这种上下文与常规的线程执行状态有所不同,具有一些独特的特点和约束,主要包括以下几个方面:

特权级别

  • 中断服务程序通常运行在更高的特权级别,如内核模式或超级用户模式,而不是用户模式。这样,中断处理程序可以直接访问硬件和系统资源,绕过用户程序的限制。

无线程调度

  • 中断上下文中,操作系统的线程调度通常是不可用的。在处理器进入中断时,操作系统通常不会进行线程的挂起、恢复或切换。中断服务程序需要尽可能快地执行并返回,以免导致系统响应延迟。因此,任何阻塞性的操作(例如,等待锁或资源)在中断处理程序中都是不允许的。

执行优先级

  • 中断服务程序具有比普通线程更高的优先级。由于硬件事件(如外部设备的输入、网络数据包的到达等)需要快速响应,中断服务程序会在系统中的其他任务之前执行,从而保证系统能迅速响应外部事件。

栈和寄存器

  • 中断服务程序运行时,使用独立的栈,与普通线程的栈是分开的。处理器的寄存器状态会在中断发生时被保存,并在中断服务程序执行完毕后恢复,以确保系统能够恢复到中断发生前的状态。

中断上下文的特殊性

中断上下文的特殊性主要体现在以下几点:

无法进行线程调度

  • 在中断上下文中,线程调度是不可用的。操作系统不能在处理中断的同时挂起当前线程或进行线程切换。如果中断处理程序需要执行的任务涉及到等待(如需要等待资源或锁),这种操作是不允许的。这是因为中断服务程序必须尽量简短和高效,以避免延迟响应其他中断或硬件事件。

中断响应性要求

  • 中断处理程序必须尽快响应外部硬件事件。如果中断处理程序中使用了传统的阻塞操作(如获取锁),可能会导致线程等待被阻塞,从而延长中断处理的时间,这会降低系统对实时事件的响应能力。为了确保系统的响应性,通常会避免在中断服务例程中进行阻塞操作。

总结

  • 中断上下文的主要特点是:运行在高特权级别、没有线程调度、需要快速响应、以及独立的栈和寄存器。
  • 由于中断处理程序的特殊性,操作系统通常避免在中断处理中使用传统的线程同步机制,如阻塞锁,以防止影响系统的实时响应性能。

5.6 为什么中断需要尽快执行

中断需要尽快执行的原因主要涉及以下几个方面:

1. 减少延迟

  • 实时性:在实时系统或对时间要求严格的应用中,及时响应中断至关重要。系统必须确保能够迅速处理外部事件,以保持系统的实时性。例如,在汽车控制系统医疗设备中,如果中断响应延迟过长,可能会导致控制失效,进而对系统的稳定性和安全性造成威胁。
  • 实时任务:一些关键任务(如运动控制、传感器读取、紧急停止等)要求在发生中断时,能立即作出反应。如果中断响应延迟,将可能导致任务无法在规定的时间内完成,进而影响到系统的准确性和安全性。

2. 避免资源占用

  • 系统资源:中断服务程序通常运行在比普通线程更高的优先级上,长时间占用 CPU 资源可能导致其他任务无法及时执行,影响系统的整体效率。例如,某些非实时任务可能无法按时处理,造成数据丢失或系统延迟。
  • 多任务调度:当中断程序占用 CPU 时间过长时,系统的任务调度可能会变得不合理,其他重要任务的执行可能会被推迟,导致系统无法在合理的时间内处理所有任务。

3. 保持硬件稳定性

  • 硬件状态:硬件设备在处理中断时,可能会产生内部状态的变化。中断处理程序如果执行时间过长,可能会导致设备状态超时或失效。例如,某些外部设备在中断时需要进行快速的响应(如传感器的读取、外设的数据同步等)。长时间的中断处理会导致设备无法正常工作,甚至发生错误。
  • 设备超时:设备通常有内部时钟或时间限制,长时间占用处理器资源会导致设备处于“超时”状态,无法进行正常的操作,从而影响设备的正常功能。

结论

中断需要尽快执行的主要原因是为了减少延迟、避免系统资源的长时间占用以及保持硬件设备的正常稳定性。中断服务程序的高效处理可以确保系统的实时性、任务调度和硬件稳定性,从而提高系统的整体性能和可靠性。

5.7 系统调用

系统调用(System Call)是操作系统提供给应用程序与内核进行交互的接口。在Linux系统中,应用程序通过系统调用与内核通信以执行一些底层操作,例如文件操作、进程管理、内存分配等。为了简化应用程序与内核的交互,应用程序通常通过库函数(如glibc)来调用系统调用。

系统调用的基本流程

  1. 用户态调用:应用程序通过调用标准库函数(如read()write())来请求执行特定的系统操作。
  2. 内核态切换:库函数内部会触发一个系统调用,它会通过中断或其他机制将控制权从用户态切换到内核态。
  3. 内核处理:内核完成实际的操作,如文件读取或写入,进程创建等。
  4. 返回用户态:内核操作完成后,结果会传回到应用程序,控制权返回用户态。

示例代码:读取文件内容

我们以文件读取为例,来展示如何通过系统调用从文件中读取数据。我们将使用 open() 系统调用打开一个文件(example.txt文件内容为:Hello, World! ),read() 系统调用读取文件内容,close() 系统调用关闭文件。

  • open("example.txt", O_RDONLY):此系统调用用于打开一个文件,这里我们以只读模式打开名为example.txt的文件。它返回文件描述符(fd),成功时返回非负整数,失败时返回-1。
  • read(fd, buffer, sizeof(buffer)-1):此系统调用用于从文件描述符fd指向的文件中读取数据,并将其存储在buffer中。它返回实际读取的字节数,或者在失败时返回-1。
  • close(fd):此系统调用用于关闭文件描述符fd,释放相关资源。
  • 文件example.txt成功打开后,read()系统调用会将文件内容读取到buffer中。
  • 读取到的字节数通过返回值bytesRead获取,并将内容输出到屏幕上。
  • 系统调用的核心实现

    每个系统调用都对应一个特定的内核服务函数。当用户程序调用标准库函数时,标准库函数会将调用参数和控制信息传递给内核,以执行相应的操作。系统调用会通过中断或软中断等机制切换到内核态进行处理。

    在Linux中,系统调用是通过一个系统调用号来标识的。不同的操作系统在内核中会维护一个系统调用表(Syscall Table),根据系统调用号执行不同的内核功能。

    系统调用的例子:

    • open():打开文件
    • read():读取文件
    • write():写入文件
    • close():关闭文件
    • fork():创建新进程
    • exec():执行程序

    总结

    • 系统调用提供了用户空间和内核空间的接口,使得应用程序能够进行各种操作系统级别的操作。
    • 每个系统调用都有其特定的功能,通过调用标准库函数,应用程序可以轻松地执行系统级操作。
    • 例子中,我们通过open()read()close()等系统调用,读取文件内容并输出。

    5.8 系统调用的作用

    系统调用是操作系统提供的一种接口,允许应用程序请求操作系统的服务。它们充当了应用程序与操作系统之间的桥梁,通过系统调用,应用程序可以执行一些只有操作系统核心(内核态)才能进行的特权操作,如文件读写、进程管理等。以下是一些常见的系统调用功能及其示例:

    提供资源访问

    应用程序通过系统调用请求访问操作系统提供的资源,如文件的读写、网络通信和设备访问。

    示例:文件读写

    假设我们有一个程序需要读取一个文件并打印文件内容。

    • fopen:系统调用打开文件。
    • fgetc:系统调用读取文件。
    • putchar:系统调用输出字符。
    • fclose:系统调用关闭文件。

    实现用户态与内核态切换

    当应用程序需要执行特权操作(如设备访问或进程管理等)时,必须由操作系统内核来完成。通过系统调用,应用程序可以将控制权从用户态切换到内核态。

    示例:进程控制

    程序通过 fork() 创建一个子进程。这个调用将控制权从用户态切换到内核态,并由内核创建子进程。

    • fork():系统调用,用于创建一个新的子进程。返回值是子进程的PID(在父进程中),在子进程中返回0。
    • 操作系统切换到内核态执行该调用,创建新进程。

    提供操作系统服务

    系统调用封装了操作系统的核心功能,包括进程管理、内存管理、文件操作等。

    示例:进程管理

    系统调用 getpid() 获取当前进程的进程ID。

    • getpid():系统调用,返回当前进程的ID。

    实现进程间通信

    进程间通信(IPC)允许不同的进程之间共享数据或信息。常见的进程间通信机制包括管道、消息队列、共享内存等。

    示例:管道通信

    进程通过管道进行通信,父进程写入数据,子进程读取数据。

    • pipe():系统调用创建管道。
    • fork():系统调用创建子进程。
    • read()write():系统调用用于从管道读取数据和向管道写入数据。

    总结

    系统调用通过提供资源访问、实现用户态与内核态切换、提供操作系统服务和实现进程间通信,极大地简化了应用程序与操作系统之间的交互。通过这些接口,应用程序能够在用户态与内核态之间进行高效、安全的切换,同时实现复杂的功能。

    5.9 当系统调用read()/write(),内核具体做了哪些事情

    在 Linux 系统中,read()write() 系统调用是常见的文件操作函数。当应用程序执行这些系统调用时,内核会进行一系列复杂的操作来实现文件的读取和写入。以下是内核执行 read()write() 时的详细步骤:

    1. 用户空间发起 read()/write() 系统调用,并将参数传递给内核

    • 用户空间的程序调用 read()write() 函数,并传递所需的参数,包括文件描述符、缓冲区以及要读取或写入的字节数。
    • 这些参数通过系统调用接口传递给操作系统内核。此时,用户态程序请求内核执行特定的操作。

    2. 内核根据系统调用号找到相应的内核函数进行处理,如 sys_read()/sys_write()

    • 每个系统调用都有一个唯一的系统调用号,内核根据这个号查找并调用对应的内核函数。例如,sys_read() 用于处理 read()sys_write() 用于处理 write()
    • 这些内核函数会处理实际的文件操作。

    3. 内核根据文件描述符找到对应的文件对象,并执行读取或写入操作

    • 内核使用传递过来的文件描述符(FD)查找对应的文件对象。文件描述符是一个整数,它指向内核维护的文件表项,每个文件表项都包含有关文件的信息,如文件位置、文件类型等。
    • 文件对象包含了文件的元数据,内核根据这个文件对象来执行进一步的操作。

    4. 读取操作:内核将数据从文件或设备读取到内核空间,并通过页缓存层进行管理

    • 对于读取操作,内核会从磁盘或其他设备中读取数据。在实际操作中,内核首先会检查页缓存(page cache),如果缓存中已有数据,内核会直接从缓存中获取数据而无需访问硬盘,这样提高了效率。
    • 如果缓存没有相关数据,内核会发出磁盘 I/O 请求,从磁盘设备中读取数据。
    • 读取的数据被存储在内核空间的缓冲区中,准备将数据传递到用户空间。

    5. 写入操作:内核将数据从用户空间拷贝到内核空间,并通过文件系统层将数据写入文件或设备

    • 对于写入操作,内核会将数据从用户空间的缓冲区复制到内核空间。
    • 然后,数据通过文件系统层传递,文件系统会将数据写入磁盘或其他设备。
    • 写入时,内核会根据文件系统的规则(如文件类型、大小等)来处理数据的存储。

    6. 内核可能会通过缓存管理、块设备管理和驱动程序等层次对数据进行处理和传输

      内核还会利用缓存管理、块设备管理和设备驱动程序来优化数据的存取。

      • 缓存管理:使用内存中的缓存来减少磁盘访问。
      • 块设备管理:对设备的读取和写入进行优化,如磁盘的调度和数据块的管理。
      • 设备驱动程序:与硬件设备交互,处理低级别的 I/O 操作。

      这些处理和传输层次保证了数据可以高效且安全地从用户空间传输到内核空间,或从内核空间传输到设备。

    7. 处理完成后,内核将结果返回给用户空间,并用户空间继续执行下一步操作

    • 在完成数据的读取或写入后,内核会将结果(如读取的字节数)返回给用户空间的程序。
    • 用户程序根据返回的结果决定是否继续处理、读取更多数据或完成文件操作。

    总结

    系统调用 read()write() 在内核中涉及多个步骤,从用户空间向内核空间传递请求,内核执行文件或设备的读写操作,经过缓存管理、块设备管理、驱动程序等多层处理,最终将结果返回给用户空间。通过这些机制,操作系统能够高效、安全地管理文件和设备 I/O 操作。

    5.10 Linux中的fork()函数作用

    fork() 是 Linux 操作系统中的一个重要系统调用,其主要作用是创建一个新的进程。通过 fork(),操作系统会将当前的进程(父进程)复制一份,生成一个几乎完全相同的新进程(子进程),子进程会继承父进程的资源和状态。以下是更详细的解释:

    1. 创建新的进程

    • 作用fork() 系统调用用于创建一个新的进程,子进程是父进程的副本,几乎拥有与父进程相同的资源和属性。
    • 行为:父进程调用 fork() 后,操作系统会创建一个与父进程相似的子进程,包括内存空间、文件描述符等,除了返回值以外,父子进程是独立的,它们有各自独立的进程 ID 和地址空间。

    2. 返回值区分父子进程

    • 父进程fork() 在父进程中返回新创建的子进程的 PID(进程 ID)。如果 fork() 成功,父进程通过该值可以知道子进程的 ID。
    • 子进程fork() 在子进程中返回 0。子进程通过这个返回值知道自己是通过 fork() 创建的。

    3. 资源和状态继承

    • 内存:子进程会继承父进程的内存内容(但它们的内存空间是独立的)。Linux 使用“写时复制”(Copy-On-Write, COW)机制来优化内存开销,即父子进程开始时共享相同的内存页面,只有在某一进程对内存做修改时,操作系统才会为它们分配独立的内存页面。
    • 文件描述符:父进程打开的文件描述符会被复制到子进程,这意味着父子进程可以访问相同的文件或设备,但它们的文件指针是独立的。

    4. 并发和多进程编程

    • 并发性fork() 使得一个进程可以并发地运行多个进程。例如,Web 服务器在处理每个客户端请求时,通常会使用 fork() 创建一个新的子进程来独立处理该请求。
    • 多进程编程:通过 fork(),可以轻松实现多进程并行处理,这对于需要处理大量任务、进行并行计算、或需要隔离不同任务的应用场景非常有用。

    5. 示例代码

    以下是一个简单的 fork() 使用示例,它展示了父子进程的区别:

  • 子进程:当 pid == 0 时,表示当前执行的是子进程。getpid() 返回当前进程的 PID,因此它打印的是子进程的 PID。
  • 父进程:当 pid > 0 时,表示当前执行的是父进程。getpid() 返回父进程的 PID,pid 返回的是子进程的 PID,因此父进程会打印出自己的 PID 和子进程的 PID。
  • 总结

    • fork() 是 Linux 中用于进程创建的系统调用,它通过复制父进程的资源和状态来创建子进程。
    • fork() 提供了并发处理的能力,使得一个进程可以分裂成多个进程,适用于多任务并行执行。
    • 父子进程拥有各自独立的地址空间和资源,但可以共享文件描述符等资源,且可以通过进程间通信进行协作。

    #牛客激励计划##嵌入式笔面经分享##嵌入式##嵌入式八股#

    作者简介:仅用几个月时间0基础天坑急转嵌入式开发,逆袭成功拿下华为、vivo、小米等15个offer,面试经验100+,收藏20+面经,分享求职历程与学习心得。 专栏内容:这是一份覆盖嵌入式求职过程中99%问题指南,详细讲解了嵌入式开发的学习路径、项目经验分享、简历优化技巧、面试心得及实习经验,从技术面,HR面,AI面,主管面,谈薪一站式服务,助你突破技术瓶颈、打破信息差,争取更多大厂offer。

    全部评论

    相关推荐

    评论
    5
    9
    分享

    创作者周榜

    更多
    牛客网
    牛客企业服务