【操作系统 / 系统编程】2025 秋招超详细复习指南 - 基于 Linux 环境的全面复习 - 持续更新(目前1.7w字)

news2024/9/29 3:24:33

操作系统 / 系统编程复习目录

  • 一、进程概念
    • 1. 冯诺依曼
      • 1.1 外设
      • 1.2 IO
      • 1.3 数据流
      • 1.4 存储分级 && IO效率
    • 2. OS
      • 2.1 作用:管理
      • 2.2 管理:先描述,再组织
      • 2.3 内存管理、进程管理、文件系统、驱动管理
    • 3. 进程
      • 3.1 什么是进程
      • 3.2 为什么要有 PCB(task_struct)
      • 3.3 task_struct 内容
      • 3.4 PID / PPID,getpid() / getppid()
      • 3.5 状态:R / S / Z / D / X / T
      • 3.6 优先级
      • 3.7 进程地址空间
        • 3.7.1 是什么?
        • 3.7.2 为什么?
        • 3.7.3 怎么办?
  • 二、进程控制
    • 1. 进程创建
      • 1.1 fork
      • 1.2 返回值
      • 1.3 写时拷贝
      • 1.4 fork 目的
      • 1.5 系统产生新进程的方式
      • 1.6 创建进程的过程
    • 2. 进程终止
      • 2.1 进程退出场景
      • 2.2 操作:_exit / exit / main return
      • 2.3 进程终止系统做了什么
    • 3. 进程等待
      • 3.1 为什么要等待:内存泄漏
      • 3.2 如何等待?- wait / waitpid
      • 3.3 signal / exit code
      • 3.4 阻塞等待 vs 非阻塞等待
    • 4. 进程替换
      • 4.1 替换原理
      • 4.2 execl、execle、execlp、execv、execve、execvp
      • 4.3 my shell
  • 三、进程通信
    • 1. IPC
    • 2. System V IPC
    • 3. 管道
    • 4. 共享内存
    • 5. 消息队列
    • 6. 信号量
  • 四、基础 IO
    • 1. FILE*
    • 2. 认识 fd
    • 3. fd 的本质
      • 3.1 数组下标:fd_array[]
      • 3.2 fd 分配规则
      • 3.3 dup、dup2
      • 3.4 输出重定向,输入重定向,追加重定向的本质与操作
    • 4. fd vs FILE*
      • 4.1 包含关系
      • 4.2 缓冲区与刷新方式
      • 4.3 系统调用 write VS 库函数 fwrite
      • 4.4 用户级缓冲区
    • 5. 文件系统
      • 5.1 磁盘
      • 5.2 分区 vs 格式化
      • 5.3 Block / Block Group / Super Block / Inode Bitmap / Inode Table / Data Blocks
      • 5.4 inode 理解:文件=内容(data)+属性(inode)
      • 5.5 软链接
      • 5.6 硬链接
    • 6. 动静态库
      • 6.1 如何打包静态库?ar rc
      • 6.2 如何打包动态库?gcc、g++ -shared、-fPIC
      • 6.3 如何使用静态库?lib*.a
      • 6.4 如何使用动态库?lib*.so
      • 6.5 include lib
      • 6.6 `-I / -L / -l`
      • 6.7 动态库:LD_LIBRARY_PATH
  • 五、信号
    • 1. 信号概念
    • 2. 信号产生的方式
    • 3. 信号发送给进程的本质
    • 4. core dump:核心转储
    • 5. 信号相关概念
    • 6. 信号相关操作
    • 7. 信号的处理
    • 8. 竞态条件
    • 9. volatile
    • 10. SIGCHLD,SIG_IGN
  • 六、多线程
    • 1. 线程理论
    • 2. 线程控制
    • 3. 线程同步与互斥
    • 4. 其他概念

在这里插入图片描述

一、进程概念

1. 冯诺依曼

我们常见的计算机、笔记本、服务器,大部分都遵守冯诺依曼体系。

在这里插入图片描述

1.1 外设

  • 输入单元:话筒,摄像头,键盘,鼠标,磁盘,网卡,…
  • 中央处理器(CPU):运算器 && 控制器;
  • 输出单元:声卡,显卡,网卡,磁盘,显示器,打印机,…

1.2 IO

程序在运行的时候,必须把程序先加载到内存,为什么?

  • 冯诺依曼体系结构是这么规定的!
  • 程序 -> 文件 -> 磁盘 -> 外设 -> 内存 -> CPU;
  • 程序 -> 指令和数据 -> CPU;
  • 在数据层面,CPU 只和 内存 打交道,外设 只和 内存 打交道。

1.3 数据流

在冯诺依曼体系中,数据流是指数据在计算机系统中的流动过程。以用户通过键盘输入数据并显示在显示器上为例,数据流的过程大致如下:

  1. 用户通过键盘输入数据,键盘作为输入设备将数据捕获并转换为电信号。
  2. 电信号通过接口电路传输到计算机内部,并被存储在内存中。
  3. CPU从内存中读取数据,并进行处理(如加密、编码等)。
  4. 处理后的数据再次被写入内存,并准备输出。
  5. 显示器作为输出设备从内存中读取数据,并将其转换为可视的图像显示在屏幕上。

1.4 存储分级 && IO效率

冯诺依曼体系中的存储系统通常被划分为多个层次,以满足不同速度和容量的需求:

在这里插入图片描述

  • 距离 CPU 越近的存储单元,效率越高,造价贵,单体容量越小;
  • 距离 CPU 越远的存储单元,效率越低,造价便宜,单体容量大;
  • 内存看作一个非常大的缓存,介于 设备 和 CPU 之间,利用内存,把效率问题,转化成为了软件问题!
  • 计算机的效率最终就变成了以内存效率为主;
  • 利用高速缓存来减少对主存的访问次数,提高数据访问速度。

2. OS

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。操作系统包括:

  • 内核(内存管理、进程管理、文件系统、驱动管理);
  • 其他程序(函数库、shell 程序、…)。

2.1 作用:管理

  • 与硬件交互,管理所有的软硬件资源;
  • 为用户程序(应用程序)提供一个良好(稳定、高效、安全)的执行环境。

在这里插入图片描述

2.2 管理:先描述,再组织

  1. 描述起来,用 struct 结构体;
  2. 组织起来,用链表或其他高效的数据结构。
  3. 把你对数据的管理场景转化成为:对特定数据结构的增删查改!

2.3 内存管理、进程管理、文件系统、驱动管理

内存管理

  • 内存分配:为新进程或进程中的新数据块分配内存空间。
  • 内存回收:当进程结束或数据不再需要时,回收其占用的内存空间。
  • 内存保护:确保每个进程只能访问自己被分配的内存区域,防止内存越界和非法访问。
  • 内存映射:将磁盘上的文件或数据块映射到进程的地址空间中,实现快速访问。
  • 内存交换(Swapping):当物理内存不足时,将部分不常用的内存数据交换到磁盘上,以释放内存空间。

进程管理

  • 进程创建:根据系统调用或程序启动请求创建新进程。
  • 进程调度:按照一定的调度算法(如时间片轮转、优先级调度等)为进程分配CPU资源。
  • 进程同步与通信:确保多个进程在并发执行时能够正确、有序地共享数据和资源。
  • 进程终止:正常或异常地结束进程的执行,回收其占用的资源。

文件系统

  • 文件存储:将用户数据以文件的形式存储在磁盘上。
  • 文件检索:根据文件名、路径等信息快速找到并访问文件。
  • 文件保护:通过权限控制、加密等手段保护文件数据的安全性和完整性。
  • 文件共享:允许多个用户或进程同时访问同一个文件。
  • 文件系统的恢复与备份:在发生数据丢失或损坏时,能够恢复或备份文件系统中的数据。

驱动管理

  • 驱动加载与卸载:在系统启动时加载必要的驱动程序,并在不需要时卸载它们。
  • 设备识别与配置:识别连接到系统的硬件设备,并根据配置信息设置其工作参数。
  • 设备通信:通过驱动程序与硬件设备进行通信,实现数据的读写和控制。
  • 错误处理:当硬件设备发生错误时,通过驱动程序向操作系统报告错误信息,并协助进行错误恢复。

3. 进程

3.1 什么是进程

  • 课本概念:程序的一个执行实例,正在执行的程序…
  • 内核观点:担当分配系统资源(CPU 时间,内存)的实体;
  • 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合
  • 课本上称之为 PCB(process control block),Linux 操作系统下的 PCB 是 task_struct
    struct PCB
    {
    	// 状态
    	// 优先级
    	// 内存指针字段
    	// 标识符
    	// ...包含进程几乎所有的属性字段
    	struct PCB* next;
    }
    

3.2 为什么要有 PCB(task_struct)

Linux 中的 PCB(task_struct)

  • 在 Linux 中描述进程的结构体叫做 task_struct
  • task_struct 是 Linux 内核的一种数据结构,它会被装载到 RAM(内存)里,并包含进程的信息;

PCB 有什么用?

  • 操作系统需要管理加载到内存的程序(进程),怎么管理?先描述,再组织!
  • 进程 = 内核 PCB 对象 + 可执行程序!
  • 未来,所有对进程的控制和操作,都只和进程的 PCB 有关,和进程的可执行程序没有关系!!
  • 对进程的管理,转化为对 PCB 对象的管理,也就是对链表的增删查改!

3.3 task_struct 内容

  • 标识符:PID(进程标识符)、TGID(线程组标识符)、UID/GID(用户/组标识符);
  • 状态信息:状态标志、退出代码和信号;
  • 优先级和调度信息:优先级、调度策略、调度实体;
  • 链接信息:进程亲缘关系、双向循环链表;
  • 内存和地址空间信息:内存管理信息、程序代码和数据指针;
  • 上下文数据:寄存器状态、堆栈信息;
  • I/O 状态信息:I/O 请求,包括分配给进程的 I/O 设备和正在被进程使用的文件列表;
  • 文件和文件系统信息:文件描述符、文件系统状态;
  • 其他信息…

3.4 PID / PPID,getpid() / getppid()

  • PID:进程 ID;
  • PPID:父进程 ID;
  • getpid() / getppid():系统调用,获取进程标识符;
  • fork():系统调用,创建子进程。

3.5 状态:R / S / Z / D / X / T

  • R (Running):进程正在运行或者准备运行(即处于就绪队列中等待CPU);
  • S (Sleeping):进程正在睡眠中,等待某个事件发生;
  • Z (Zombie):僵尸进程。这是一个已经结束(terminated)的进程,但是其父进程还没有通过 wait()waitpid() 系统调用来读取它的结束状态。僵尸进程仍然保留在进程表中,但已经不再占用系统资源(除了进程表中的一个条目);
  • D (Disk Sleep):不可中断睡眠状态。这通常表示进程正在等待 I/O 操作,而且这个等待不能被信号中断。这种状态通常用于等待磁盘 I/O 操作的进程;
  • X (Dead):死亡状态。这个状态只是一个返回状态,你不会在任务列表里看到这个状态;
  • T (Stopped):进程被停止或追踪。一般通过发送 SIGSTOP 信号来停止进程,并且可以发送 SIGCONT 信号让进程继续运行。

3.6 优先级

ps -l	# 查看进程信息

F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1000 1670858 1670857  0  80   0 -  2069 do_wai pts/0    00:00:00 bash
0 R  1000 1670910 1670858  0  80   0 -  2202 -      pts/0    00:00:00 ps
  • UID:代表执行者的身份;
  • PID:这个进程的代号;
  • PPID:这个进程的父进程代号;
  • PRI:优先级,值越小越早被执行
  • NI:进程的 nice 值

PRI(Priority)and NI(Nice value)

  • PRI 代表进程的优先级(Priority),是 Linux 内核用于决定先处理哪些进程的一个指标;
  • 一般来说,PRI 的值越小,表示进程的优先级越高,越有可能被优先执行。
  • NI 代表进程的 nice 值,是一个用于动态调整进程优先级的数值;
  • nice 值的取值范围是 -20 到 19。其中,-20 表示最高优先级(即最低 nice 值),而 19 表示最低优先级(即最高 nice 值);
  • 作用:nice 值通过影响 PRI 值来间接影响进程的优先级。具体来说,PRI 值等于某个基准值(如 120)加上 nice 值;
  • 所以,在 Linux 下调整进程优先级,就是调整进程 nice 值!
nice	# 用于在启动新进程时设置其nice值
renice	# 用于更改已运行进程的nice值

3.7 进程地址空间

在这里插入图片描述
mm_struct

  • mm_struct 是 Linux 内核中用于管理进程内存空间的一个关键数据结构,也被称为内存描述符(memory descriptor);
  • 用于描述一个进程的虚拟地址空间,包括进程的内存映射情况、内存区域的属性、内存使用情况、页表等信息;
  • 进程间共享内存空间,实际就是在共享 mm_struct!结构内部会使用引用计数,来记录当前有几个进程共享此空间,引用计数为 0 则销毁该结构。
3.7.1 是什么?
  • 核心:以软件方式模拟内存!
  • 进程地址空间是进程在运行时所拥有的一个独立的虚拟内存区域,它包含了进程运行所需的代码、数据、堆栈等;
3.7.2 为什么?
  • 内存独占:每个进程都有自己独立的地址空间,这保证了进程间的内存隔离,防止了一个进程对另一个进程内存的非法访问;
  • 保护内存:Linux为 每个内存区域(如代码段、数据段、栈等)设置了访问权限,进程只能按照设定的权限访问这些内存区域;而虚拟内存的设计防止了进程直接访问物理内存,增加了系统的安全性和稳定性;
  • 统一布局:Linux 进程地址空间具有统一的结构和布局(代码段、数据段、堆、栈、…),这种设计简化了内存管理,提高了系统的运行效率。
3.7.3 怎么办?

在这里插入图片描述

  1. 虚拟地址:是程序运行时所使用的地址,对程序来说是透明的,每个进程都认为自己拥有完整的地址空间。虚拟地址不是直接映射到物理内存上的,而是通过一系列的机制(如页表)来间接访问物理内存;
  2. 物理地址:是真实存在的内存地址,用于 CPU 直接访问物理内存。物理地址由内存管理单元(MMU)将虚拟地址转换而来;
  3. 页表:是虚拟地址到物理地址的映射表,存储了虚拟页号(VPN)到物理页号(PPN)的映射关系。每个进程都有自己独立的页表,确保了进程的独立性和隔离性;
  4. 页表项(PTE):页表中的每一个条目称为页表项,包含了有效位、物理页号等信息。有效位用于标识该虚拟页是否已在物理内存中,物理页号则指向实际的物理内存地址;
    • r(Read):读属性。在页表中,每个页表项(PTE)可以包含读权限标志位。如果设置了读权限(r=1),则允许对该页进行读取操作;如果未设置(r=0),则尝试读取该页将引发异常;
    • w(Write):写属性。与读属性类似;
    • readonly:只读属性。这不是页表项中的一个直接属性,但可以通过设置页表项的读权限(r=1)和清除写权限(w=0)来实现;
    • 脏页:当页的内容在内存中被修改后,该页被视为“脏”的,直到其内容被写回磁盘;
    • 多级页表:当虚拟地址空间非常大时,使用单级页表会导致页表过大,难以管理。因此,引入了多级页表结构。多级页表将虚拟地址空间划分为多个层次,每一级页表都指向下一级页表的地址或物理页的地址;
    • 页帧/页框(4KB):在物理内存中,页帧(或页框)是指用于存储页内容的连续内存块页表项中存储的是页帧的物理地址,用于将虚拟地址映射到物理地址。
  5. MMU(内存管理单元):是负责虚拟地址到物理地址转换的硬件单元。当 CPU 执行指令时,会产生一个虚拟地址,这个地址被传递给 MMU。MMU 通过查询页表,找到对应的物理地址,并将其返回给 CPU 进行内存访问。
    • 正常转化:CPU 生成虚拟地址 -> MMU 查询页表 -> 构建物理地址 -> CPU 访问物理内存;
    • 错误转化:缺页异常,权限检查;
  6. 映射机制:通过页表和 MMU 的协作,实现了虚拟地址到物理地址的映射。这种映射机制确保了进程的独立性和隔离性,同时也提高了内存的使用效率和安全性;
  7. 动态映射:在程序运行过程中,系统可以根据需要动态地加载和卸载内存页,实现了对内存资源的有效管理。这种动态映射机制也支持了虚拟内存的实现,使得程序可以访问比物理内存更大的内存空间。

虚拟地址空间布局问题

  • 虚拟地址空间通常被划分为用户空间(User Space)和内核空间(Kernel Space)两部分;
  • 用户空间通常包含:代码段、数据段、BSS段、堆、栈、件映射和匿名映射段;
  • 采用多级页表结构来减少内存的使用并提高页表查找的效率;
  • 页表项中存储的是页帧的物理地址,用于将虚拟地址映射到物理地址。

二、进程控制

1. 进程创建

1.1 fork

#include <unistd.h>
pid_t fork(void);
  • fork() 是 Linux 中很重要的一个函数,它可以从已存在的进程中创建一个新进程,新进程为子进程,而原进程为父进程;

进程调用 fork(),当控制权转移到内核中的 fork 代码后,内核会:

  1. 分配新的内存块和内核数据结构给子进程;
  2. 将父进程部分数据结构内容拷贝给子进程;
  3. 添加子进程到系统进程列表当中;
  4. fork 返回,开始调度器调度…

在这里插入图片描述

1.2 返回值

  • 子进程中返回 0;
  • 父进程中返回 子进程的 pid;
  • 出错返回 -1。

1.3 写时拷贝

  • 通常,父子代码是共享的;
  • 父子在不写入时,数据就是共享的,当任意一方试图写入,就会以写时拷贝的方式,各自持有一份副本;

在这里插入图片描述

1.4 fork 目的

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求;
  • 一个进程要执行一个不同的程序。例如:子进程从 fork 返回后,调用 exec(进程替换)函数;
  • 守护进程也会用到 fork。

1.5 系统产生新进程的方式

  1. 操作系统直接创建,如:系统进程;
  2. 由父进程创建:
    • fork():通过复制当前进程(父进程)来创建一个新的子进程。子进程会继承父进程的大部分属性和数据,但拥有独立的进程ID(PID)和内存空间;
    • vfork():与 fork() 类似,但子进程会共享父进程的内存空间,并且父进程会被阻塞,直到子进程调用 exec() 或 exit();
    • clone():这是一个更灵活的系统调用,允许在创建新进程时选择性地共享父进程的资源;
    • exec() 系列函数:虽然 exec() 本身不直接创建新进程,但它可以将当前进程替换为新的可执行文件,从而间接地创建了一个新的执行环境;

1.6 创建进程的过程

  1. 系统调用;
  2. 分配进程标识符(PID);
  3. 创建进程控制块(PCB);
  4. 复制或共享资源;
  5. 初始化新进程;
  6. 调度新进程;
  7. 执行新程序(可选);
  8. 父进程和子进程的交互。

2. 进程终止

2.1 进程退出场景

  1. 正常:程序执行完毕,或遇到特定的退出指令(return),进程会正常结束;
  2. 代码结束结果不对
  3. 异常:程序执行过程中遇到无法处理的错误或异常,导致进程非正常终止。例如,访问非法内存地址、除零错误等;
  4. exit code(退出码):是进程终止时返回给操作系统的一个整数值,用于表示进程的执行结果或状态。退出码为 0 通常表示成功或正常退出,而非零值则表示某种形式的错误或异常情况。

2.2 操作:_exit / exit / main return

_exit

#include <unistd.h>
void _exit(int status);

参数: status定义了进程的终止状态, 父进程通过wait来获取该值
  • _exit 直接通过系统调用进入内核,终止进程;
  • 立即终止进程,不执行任何清理操作;
  • 调用 _exit 后,进程占用的资源将被操作系统回收。

exit

#include <unistd.h>
void exit(int status);
  • exit 是一个 C 库函数,用于终止当前进程;
  • 执行清理操作,包括调用退出处理程序和刷新 I/O 缓冲区;
  • 最后调用 _exit 终止进程。

在这里插入图片描述

return

  • return 是一种更常见的退出进程方法;
  • 执行 return 0 等价于 exit(0),因为调用 main 的运行时函数会将 main 的返回值当作 exit 的参数。

2.3 进程终止系统做了什么

  1. 终止遗留线程,如“孤儿”线程;
  2. 释放资源:系统会释放进程所分配的所有资源,包括内存、文件描述符、内核对象等;
  3. 执行清理操作:例如,刷新标准 I/O 流的缓冲区,确保所有待输出的数据都被写出;
  4. 设置进程状态:系统会将终止的进程设置为僵死状态,直到其父进程通过某种方式(wait、waitpid)回收其资源并获取其退出状态;
  5. 进行 CPU 再分配:将 CPU 分配给其他等待运行的进程。

3. 进程等待

3.1 为什么要等待:内存泄漏

  • 子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏
  • 并且我们无法再去杀死一个僵尸进程;
  • 父进程也需要通过子进程的退出码,了解子进程的执行情况;
  • 父进程就通过进程等待的方式,回收子进程资源,获取子进程退出信息

3.2 如何等待?- wait / waitpid

wait

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);

返回值:
	成功返回被等待进程pid, 失败返回-1
参数:
	输出型参数, 获取子进程退出状态, 不关心则可以设置为NULL

wait pid

pid_t waitpid(pid_t pid, int* status, int options);

返回值:
	正常返回时, waitpid返回收集到的子进程的进程ID
	如果设置了选项WNOHANG, 而调用中waitpid发现没有已退出的子进程可收集, 返回0
	如果调用中出错, 则返回-1, 这时errno会被设置为对应的错误值

参数:
	pid:
		pid = -1, 等待任意一个子进程, 类似wait
		pid > 0, 等待进程ID与pid相等的子进程
	status:
		WIFEXITED(status): 若子进程为正常终止, 则为真。(查看进程是否正常退出)
		WEXITSTATUS(status): 若WIFEXITED为非零, 提取子进程退出码。(查看进程的退出码)
	options:
		WNOHANG: 若pid指定的子进程没有结束,waitpid()函数返回0, 不予以等待。 若正常结束, 则返回该子进程的ID

注意事项

  • 如果子进程已经退出,调用 wait / waitpid 时,会立即返回并释放资源,获得子进程的退出信息;
  • 如果调用 wait / waitpid 时,子进程存在且正常运行,则进程可能阻塞;
  • 如果不存在该子进程,则立刻出错返回。
    在这里插入图片描述

3.3 signal / exit code

  • Signal 是一种进程间通信机制,用于通知进程发生了某个事件;
  • Exit Code 是进程结束执行时返回给操作系统的一个整数值,用于表示进程的退出状态;
  • Signal 用于进程间的异步通信,通知进程发生了某个事件;而 Exit Code 用于表示进程的退出状态,供父进程判断子进程的执行结果;
  • Signal 可以在进程执行的任何时刻由系统或其他进程产生;而 Exit Code 只在进程结束执行时产生;

3.4 阻塞等待 vs 非阻塞等待

阻塞等待

  1. 挂起线程:在等待期间,当前线程无法执行其他操作,必须等待条件满足;
  2. 资源占用:阻塞的线程会占用系统资源,包括 CPU 调度时间片等,直到它被唤醒;
  3. 性能影响:在高并发场景下,大量的阻塞等待可能导致系统资源耗尽,影响程序的性能和响应速度。

非阻塞等待

  1. 线程继续执行:在等待期间,当前线程可以执行其他操作,提高了程序的并发性和响应速度;
  2. 轮询机制:非阻塞模式通常需要程序自己实现轮询机制来检查条件是否满足;
  3. 复杂度高:与阻塞模式相比,非阻塞模式的编程复杂度更高,需要处理更多的逻辑和状态管理。

4. 进程替换

4.1 替换原理

  • 父进程用 fork() 创建子进程后,子进程可以通过调用 exec() 系列函数以执行另外一个程序;
  • 当子进程调用 exec() 函数时,该进程的代码和数据完全被新程序替换,替换完成后,新程序将在当前进程的上下文中开始执行;
  • 调用 exec() 并不创建新进程,所以调用 exec() 前后该进程的 id 并未改变。

在这里插入图片描述

会不会创建新进程

  • 不会
  • 创建一个进程,是先创建PCB、地址空间、页表等,再把程序加载到内存;
  • 而程序替换所做的本质工作,就是加载

后续代码如何处理

  • 程序替换一旦成功,原进程的后续代码将不再执行,代码和数据都会被丢弃!
  • 如果 exec 调用失败(即返回 -1),那么原程序将继续执行 exec 之后的代码。

4.2 execl、execle、execlp、execv、execve、execvp

#include <unistd.h>

int execl(const char* path, const char* arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char* path, const char* arg, ..., char* const envp[]);
int execv(const char* path, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execve(const char* path, char* const argv[], char* const envp[]);

函数解释

  • 这些函数如果调用成功,则执行新的程序,不再返回;
  • 如果调用出错,返回 -1,继续执行原程序;
  • exec() 只有出错的返回值,没有成功的返回值

命名理解

  • l(list):表示参数采用列表;
  • v(vector):参数采用数组;
  • p(path):自动搜索环境变量PATH;
  • e(env):表示自己维护环境变量。

在这里插入图片描述

4.3 my shell

实现一个简易 shell …

三、进程通信

1. IPC

2. System V IPC

3. 管道

4. 共享内存

5. 消息队列

6. 信号量

四、基础 IO

1. FILE*

  • FILE* 是 C 语言中用于文件操作的一个非常重要的指针类型,FILE* 指向一个 FILE 类型的对象,这个对象包含了进行文件操作所需的信息;
  • 在 C 语言中,几乎所有的文件操作(打开文件、读取文件、写入文件、定位文件指针、关闭文件等)都是通过 FILE* 类型的指针来进行的;

C 中的文件操作

  1. 打开文件:使用 fopen() 函数打开文件,该函数返回一个 FILE* 类型的指针,指向打开的文件。如果文件打开失败,则返回 NULL;

    FILE *fp = fopen("example.txt", "r"); // 打开文件以只读模式  
    if (fp == NULL)
    {  
        // 错误处理  
    }
    
  2. 读写文件:使用如 fgetc()fgets()fputc()fputs()fread()fwrite() 等函数进行文件的读写操作;

    char buffer[100];  
    if (fgets(buffer, 100, fp) != NULL)
    {  
        // 成功读取一行  
    }  
    
    fputs("Hello, World!", fp); // 写入文件
    
  3. 定位文件指针:使用 fseek()ftell()rewind() 等函数可以移动文件指针到指定位置、获取当前文件指针的位置或重置文件指针到文件开头;

    fseek(fp, 0, SEEK_END); // 将文件指针移动到文件末尾  
    long pos = ftell(fp);   // 获取当前文件指针的位置  
    rewind(fp);             // 将文件指针重置到文件开头
    
  4. 关闭文件:使用 fclose() 函数关闭文件;

    fclose(fp);
    

2. 认识 fd

  • 文件描述符(File Descriptor,简称 fd)是一个非负整数
  • fd 用于在操作系统中唯一标识一个 打开的文件 或 其他输入 / 输出资源(如管道、套接字等);
  • Linux 进程默认会打开三个文件描述符,分别为标准输入 0、标准输出 1、标准错误 2;
    在这里插入图片描述

3. fd 的本质

3.1 数组下标:fd_array[]

  • 文件描述符(fd)的本质就是数组下标!
  • 操作系统要管理我们打开的文件,就是创建了相应的数据结构(file 结构体)来描述目标文件,然后把它们组织起来(files_struct);
  • 进程中包含了一个指针(*files)指向 files_struct 这张表,这张表内部包含了一个指针数组(file* fd_array[]),其中每个元素都是一个指向已打开文件的指针
  • 所以,文件描述符就是该指针数组(file* fd_array[])的下标,只要拿着文件描述符,就可以找到对应的文件

3.2 fd 分配规则

  • 最小的没有被使用的数组下标,会分配给最新打开的文件!

3.3 dup、dup2

  • 在 Linux 系统编程中,dup()dup2() 是两个非常有用的系统调用,它们用于复制文件描述符;

dup()

#include <unistd.h>  
int dup(int oldfd);

参数:
	oldfd: 是想要复制的文件描述符
返回值:
	成功时,返回一个新的文件描述符(非负整数),这个新描述符是oldfd的副本
	出错时,返回-1,并设置errno以指示错误
  • dup() 系统调用用于创建一个新的文件描述符,该描述符是调用进程中某个现有文件描述符的副本。新文件描述符与原始文件描述符指向相同的打开文件,共享相同的文件偏移量、文件状态标志和文件模式;
  • dup() 的主要用途是当需要额外的文件描述符来引用同一文件时,或者是在进行文件描述符重定向时。

dup2()

#include <unistd.h>  
int dup2(int oldfd, int newfd);

参数:
	oldfd:是想要复制的文件描述符
	newfd:是新的文件描述符的数值
返回值:
	成功时,返回newfd
	出错时,返回-1,并设置errno以指示错误
  • dup2() 系统调用类似于dup(),但它允许调用者指定新文件描述符的数值。如果指定的新文件描述符 newfd 已经打开,则 dup2() 会先关闭它,然后再创建 oldfd 的副本;
  • dup2() 主要用于重定向一个文件描述符到另一个已存在的文件描述符上,这在处理文件描述符时提供了更大的灵活性。例如,在子进程中重定向标准输出(stdout,文件描述符为1)到一个文件或管道。

3.4 输出重定向,输入重定向,追加重定向的本质与操作

  • 输出重定向是指将原本应该输出到屏幕(通常是标准输出STDOUT,文件描述符为1)的数据信息写入到指定的文件中;
  • 输入重定向是指将原本应该从标准输入(STDIN,文件描述符为0)读取的数据来源改为从指定的文件中读取;
  • 追加重定向与输出重定向类似,但它不会覆盖目标文件的内容,而是将新的数据追加到文件的末尾。
    在这里插入图片描述
重定向类型符号本质操作示例
输出重定向>将标准输出重定向到文件command > file.txt
错误输出重定向2>将错误输出重定向到文件command 2> error.log
输入重定向<将标准输入重定向到文件command < file.txt
追加重定向>>将输出追加到文件末尾command >> file.txt

4. fd vs FILE*

4.1 包含关系

  • 因为 IO 相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过 fd 访问的;
  • 所以 C 库中的 FILE 结构体内部,必定封装了 fd

4.2 缓冲区与刷新方式

  • 调用系统调用是有时间成本的,缓冲区设计是一种以空间换时间的方法;
  • C 库函数(printffwrite)会自带缓冲区,而系统调用(write)没有缓冲区
  • 库函数在系统调用的“上层”,是对系统调用的封装,所以缓冲区就是在封装时被加上的,由 C 标准库提供;
  • 我们这里说的缓冲区都是用户级缓冲区

刷新方式

  • 缓冲区会在特定条件下被刷新,例如:缓冲区写满、遇到换行符、显式调用 fflush 函数等;
  • 进程退出的时候,也会自动刷新缓冲区!

4.3 系统调用 write VS 库函数 fwrite

  • write 系统调用:是 Linux 系统中的一个系统调用,用于将数据写入文件描述符指向的文件。它工作在较低级别,不涉及用户级缓冲;
  • fwrite 库函数:是 C 标准库中的函数,用于向 FILE* 指定的文件写入数据块。fwrite 操作会利用 FILE* 结构体中的缓冲区,并在内部使用 write 系统调用来将数据从缓冲区刷新到文件中。

4.4 用户级缓冲区

  • 我们常说的缓冲区一般都是指用户级缓冲区,它是语言层面的缓冲区,C 语言自带缓冲区;
  • 使用 FILE* 时,用户级缓冲区由标准 I/O 库自动管理
  • 使用 文件描述符(fd) 时,虽然内核中可能也存在缓冲区,但这些缓冲区对用户透明,用户无法直接控制,所以不在我们讨论范围之内。

5. 文件系统

5.1 磁盘

在这里插入图片描述

  • 系统中大部分的文件都是没有被打开的,它们保存在磁盘(SSD)中;
  • OS 需要管理磁盘上的文件,那么如何在磁盘上快速定位一个文件?通过 CHS 定位法!
    • 通过磁头定位:磁道 / 柱面 - Cylinder
    • 使用哪一个磁头 - Head
    • 哪一个扇区 - Sector
  • 那么任何一个文件,不就是多个扇区承载的数据吗?
  • 把每个扇区(4KB),简单理解为一个数组的元素,那么操作系统对磁盘的管理,就变成了对数组的增删查改
    在这里插入图片描述

5.2 分区 vs 格式化

分区:将硬盘或其他存储设备划分为一个或多个逻辑区域的过程。每个分区都被视为一个独立的存储设备,拥有自己的文件系统和存储空间。

  1. 方便数据组织和管理
  2. 减少文件碎片,提升访问速度
  3. 提高系统安全和稳定,某个分区出问题,其他分区不受影响;
  4. 支持多操作系统,可以在不同分区安装多个操作系统;
  5. 简化备份和恢复过程

格式化:指在分区上创建文件系统的过程。它主要是创建文件系统的结构和元数据信息,为分区提供一个可读写的文件系统,使操作系统能够有效地与硬盘交互。

  1. 创建一个可用于存储数据的文件系统结构;
  2. 初始化分区的元数据,如文件节点表、目录项等;
  3. 清理分区上的数据,为新的数据存储做准备。

5.3 Block / Block Group / Super Block / Inode Bitmap / Inode Table / Data Blocks

在这里插入图片描述

  • Block:是文件存取的最小单位,也是操作系统读取硬盘时的基本单位;
  • Block Group:ext2 文件系统会将分区划分为数个 Block Group,方便对其进行管理;
  • Super Block(超级块):存放文件系统本身的结构信息,如果 Super Block 的信息被破坏,整个文件系统结构就被破坏了;
  • Inode Bitmap:是一个位图,每个 bit 位表示一个 inode 是否空闲可用;
  • Inode Table:存放文件属性(文件大小、所有者、最近修改时间等);
  • Data Blocks:存放文件内容。

5.4 inode 理解:文件=内容(data)+属性(inode)

  • inode 就是文件系统中的一个数据结构,其中存储了 除文件名和数据内容之外 的所有文件或目录的信息;
  • 每个文件或目录都有一个唯一的 inode,通过 inode 可以快速定位和管理文件;
  • 可以使用 ls -i 查看文件/目录的 inode;
$ touch fileT
$ ls -i
263466 fileT

在这里插入图片描述

文件 fileT 的 inode 为 263466

解释一下上图中创建新文件所需的 4 个操作

  1. 存储属性:内核先找到一个空闲的 inode(假设是 263466),并把文件信息记录到其中;
  2. 存储数据:假设这个文件需要占用三个磁盘块(Block),内核找到三个空闲块:300、500、800,将内核缓冲区中的内容复制到其中;
  3. 记录分配情况:内核在 inode 中的磁盘分布区记录了文件所占用的块列表;
  4. 添加文件名到目录:我们刚才创建的文件名为 fileT,Linux 如何在当前目录中记录这个文件?内核将(263466,fileT)这组数据添加到目录文件中;

这样一来,文件名 和 inode 之间的对应关系,就将文件名文件内容及属性连接起来了;

但是我们上面说道 文件=内容(data)+属性(inode),又该如何理解

  • inode 中是不包含 文件名 和 文件内容 的,那么一个文件应该=文件名+内容+属性才合理呀;
  • 实际上目录也是一个文件(Linux 下一切皆文件)!目录中存储的内容(data)正是该目录下 文件名 与 inode 之间的映射关系
  • 所以我们之前对于文件名的理解是不全面的,文件名 与 inode 的映射关系已经被存储在目录中了,它们是一体的!

5.5 软链接

  1. 独立文件,有独立的 inode
    • 软链接本质是一个独立文件,这个文件中保存了 目标文件的路径
    • 在访问软链接时,系统会解析软链接中存放的路径,并使用这个路径访问被链接的文件;
    • 可以对目录创建软链接;
    • 软链接主要用于在不同位置快速访问文件或目录。
  2. ln -s
    $ touch test.txt
    $ ln -s test.txt link.soft		# 创建软链接
    $ ls -l
    total 0
    lrwxrwxrwx 1 ubuntu ubuntu 8 Aug 22 23:05 link.soft -> test.txt
    -rw-rw-r-- 1 ubuntu ubuntu 0 Aug 22 15:27 test.txt
    
    • 使用 ln -s 命令为 test.txt 创建一个软链接;
  3. 快捷方式
    • 软链接类似于 Windows 下的快捷方式!

5.6 硬链接

  1. 非独立文件,没有独立的 inode

    • 硬链接本质就是在指定的目录下,插入新的 文件名与目标文件的映射关系,并让 inode 的 引用计数++;
    • 在磁盘上,找到文件靠的不是文件名,而是 inode,在 Linux 上允许将多个文件名对应(硬链接)于同一个 inode;
    • 不能对目录创建硬链接(因为这会引入循环引用的复杂性和安全问题);
  2. ln

    $ touch test.txt
    $ ln test.txt link.hard		# 创建硬链接
    
    • 使用 ln 命令为 test.txt 创建一个硬链接;
    • 现在 test.txt 和 link.hard 是同一个 inode 的文件名,这两个文件名代表同一个文件;
    • 一个 inode 对应的所有文件名(硬链接)都被删除,文件内容才会被删除(引用计数思想);
  3. ls -l

    $ ls -l
    total 0
    -rw-rw-r-- 2 ubuntu ubuntu 0 Aug 22 15:27 link.hard
    -rw-rw-r-- 2 ubuntu ubuntu 0 Aug 22 15:27 test.txt
               ^ 表示该文件有2个硬链接
    
    • 判断一个目录下有多少个子目录:硬链接数 -2 即可得到!

6. 动静态库

6.1 如何打包静态库?ar rc

6.2 如何打包动态库?gcc、g++ -shared、-fPIC

6.3 如何使用静态库?lib*.a

6.4 如何使用动态库?lib*.so

6.5 include lib

6.6 -I / -L / -l

6.7 动态库:LD_LIBRARY_PATH

五、信号

1. 信号概念

2. 信号产生的方式

3. 信号发送给进程的本质

4. core dump:核心转储

5. 信号相关概念

6. 信号相关操作

7. 信号的处理

8. 竞态条件

9. volatile

10. SIGCHLD,SIG_IGN

六、多线程

1. 线程理论

2. 线程控制

3. 线程同步与互斥

4. 其他概念


To be continued...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066730.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数学建模2024国赛时间及事项安排

2024年的全国大学生数学建模竞赛即将拉开帷幕。考虑到许多同学可能是首次参与此类赛事&#xff0c;尚不清楚如何进行有效的时间安排&#xff0c;博主在此整理了以往参赛的经验和时间管理策略&#xff0c;希望能为大家提供一些有益的参考&#xff0c;更从容地应对国赛。 本届全国…

定制开发AI智能名片商城小程序:重塑品牌曝光的创新推手

摘要&#xff1a;随着移动互联网技术的飞速发展&#xff0c;小程序作为一种轻量级应用形态&#xff0c;正逐步成为企业品牌传播与商业变现的重要渠道。本文将探讨在品牌定位中&#xff0c;如何将“定制开发AI智能名片商城小程序”作为品牌曝光的核心推手&#xff0c;通过强化品…

pikachu-Cross-Site Scripting通过攻略

反射型xss(get) 第一步&#xff1a;进入先将maxlength中的20修改大一些&#xff0c;以便我们可以输入更多的字符 第二步&#xff1a;输入<script>alert(1)</script>成功爆破 反射型xss(post) 第一步&#xff1a;点击提示得到用户名和密码登录 第二步&#xff1…

轻松备份和共享照片的Circled.me

什么是 Circled.me &#xff1f; Circled.me 旨在帮助人们在自己的服务器上轻松备份和共享照片、视频、相册。注重性能、低占用空间以及易于实施和使用。支持托管社区所需的一切&#xff0c;能够进行交流和交换照片、想法等&#xff0c;实现通过群聊等方式与您的圈子分享。 软件…

电脑资料被删除怎么办?这些方法帮你轻松找回!

在日常工作和生活中&#xff0c;电脑资料的安全存储至关重要。然而&#xff0c;有时我们可能会因为误操作或其他原因不小心删除了重要资料。这时&#xff0c;我们该如何应对呢&#xff1f;本文将为你提供几种有效的恢复方法&#xff0c;帮你轻松找回被删除的电脑资料。 一、撤…

MySQL如何判断一个字段里面是否包含汉字

SQL查询中&#xff0c;length() 和 char_length() 都是用来获取字符串长度的函数 在单字节字符集下&#xff08;如ASCII&#xff09;&#xff1a;每个字符通常占用1个字节&#xff0c;因此length()和char_length()在这类字符集中给出的结果是一样 在多字节字符集下&#xff0…

湖北省各市各地两化融合贯标、3A级认定申报奖补补助、申报条件材料、流程指南

盘点湖北省各市各地两化融合贯标、3A级认定申报奖补补助、申报条件材料、流程等内容&#xff0c;武汉市、黄石市、十堰市、宜昌市、襄阳市、鄂州市、荆门市、孝感市、荆州市、黄冈市、咸宁市、随州市,恩施土家族苗族自治州、仙桃市、潜江市、天门市、神农架林区有需要具体了解的…

OPPO手机短信删除了怎么恢复?三大解决办法助你找回

在快节奏的生活中&#xff0c;手机短信作为我们日常沟通的重要工具之一&#xff0c;往往承载着许多重要的信息&#xff0c;如验证码、银行通知、会议安排等。然而&#xff0c;不小心误删短信的情况时有发生&#xff0c;特别是对于OPPO手机用户而言&#xff0c;短信一旦删除&…

谷粒商城实战笔记-240~243-商城业务-购物车-页面环境搭建

文章目录 一&#xff0c;页面调整1&#xff0c;详情页增加“加入购物车”按钮 二&#xff0c;添加购物车后台实现详细步骤异步处理的优点 三&#xff0c;解决加购重复提交问题 这部分的主要内容&#xff1a; 从product模块的详情页点击加入购物车&#xff0c;发送请求到cart购物…

Kubernetes服务发布基础

一、Service 1.service基本介绍 service为一组提供服务的pod提供抽象的稳定的网络访问地址&#xff0c;主要用于网络服务&#xff0c;通过service定义&#xff0c;为客户端提供访问地址和负载均衡&#xff0c;屏蔽endport的变化。 在 kubernetes 中,pod 的IP 地址是动态变…

大数据面试-Zookeeper

你对Zookeeper的选举机制了解吗&#xff1f;为什么zk节点个数推荐奇数台&#xff1f;zk第一次启动的选举的细节了解吗&#xff1f; ‌ZooKeeper的选举机制‌是基于Paxos算法的一种分布式选举算法&#xff0c;用于在ZooKeeper集群中选择一个节点作为Leader&#xff0c;负责处理…

做外贸如何判断国外采购商公司规模

判断客户公司的规模&#xff0c;对于业务员来说很重要&#xff0c;这样在谈价格以及其他条款的时候才能掌握主动。一般要怎么去判断客户公司的规模呢?我们都是做实事的&#xff0c;实际经验很重要&#xff0c;做过和没做过的看多了就知道。最基本的信息是公司的注册时间及相关…

【python】Python中通过WHL文件离线安装需要的包最全面讲解

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【oracle】工具访问oracle提示-IO 错误: Got minus one from a read call

问题描述&#xff1a; 使用工具访问数据库时&#xff0c;提示IO 错误: Got minus one from a read call&#xff0c;在服务器上检查数据库正常&#xff0c;监听正常&#xff0c;连接数合理&#xff0c;防火墙没限制。最后定位sqlnet.ora配置限制了客户端访问。 解决&#xf…

四川财谷通信息技术有限公司解锁抖音小店新机遇

在数字经济蓬勃发展的今天&#xff0c;电商平台已成为推动商业创新、促进消费升级的重要力量。其中&#xff0c;抖音小店凭借其庞大的用户基础、精准的算法推荐以及高度活跃的社区氛围&#xff0c;迅速崛起为众多商家青睐的创业与营销新阵地。四川财谷通信息技术有限公司&#…

Groovy DSL从入门到项目实战(一)

Groovy是一门很灵活的Java扩展语言&#xff0c;支持弱类型、闭包、函数式编程等脚本语言的高级特性。因为小卷所在公司的船申报系统需要重构&#xff0c;对原先java硬编码的各种表单数据校验、后台业务校验使用规则脚本的形式进行剥离出来。而市面上像Jboss Drools这样的规则引…

docker-compose安装sentry

官方文档 https://develop.sentry.dev/self-hosted/ 一、前提 服务器配置至少4C16G&#xff0c;否则起不来 二、安装docker https://blog.csdn.net/weixin_45112997/article/details/134532660 三、安装docker-compose docker-compose版本有要求&#xff0c;必须大于2.…

ISO7841标准数字隔离器在现代电子系统中的作用

在快速发展的电子领域&#xff0c;隔离元件在确保各种系统的安全性、可靠性和性能方面发挥着关键作用。其中&#xff0c;光耦合器是提供电气隔离同时允许电路不同部分之间进行信号传输的关键设备。ISO7841数字隔离器尤其体现了现代电子设计所需的先进功能和多功能性。 ISO7841数…

刷题刷题刷题

89. 格雷编码 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<int> grayCode(int n) {vector<int> res;res.push_back(0);int head 1;for (int i 0; i < n; i) {for (int j res.size() - 1; j > 0; j--) {res.push_back(head r…

电脑录屏怎么录?强烈推荐这5款高清录屏软件

现在电脑录屏作为数字化生活的一项实用技能&#xff0c;正逐渐走进越来越多人的视野&#xff0c;无论是想要录制游戏直播的高光时刻&#xff0c;还是制作教学视频分享知识&#xff0c;亦或是记录会议内容以备不时之需&#xff0c;电脑录屏都能轻松满足我们的需求。 今天就给大家…