20231326《密码系统设计》第九周预习报告
20231326《密码系统设计》第九周预习报告
学习内容
《Head First C 嗨翻 C 语言》第 9 章
一、 系统调用和内核 (System Calls and Kernel)
- 系统调用 (System Call): C程序与操作系统内核沟通的机制 。它是操作系统内核中的函数,C标准库的大部分功能(例如
printf())都依赖于它们来与硬件交互 。 - 内核 (Kernel): 计算机中最重要的程序,负责管理三方面:
- 进程 (Processes): 加载程序到内存并创建进程,确保它们获得所需资源,并监控进程状态 。
- 存储器 (Memory): 分配和管理每个进程的存储器大小,并可能使用磁盘交换来增加虚拟存储器空间 。
- 硬件 (Hardware): 通过设备驱动与计算机上的设备(如键盘、屏幕)进行交互 。
二、 system() 函数
- 功能: 接收一个字符串参数,并将其作为命令执行 。
- 用途: 是在代码中运行其他程序的捷径,尤其适用于建立快速原型,可以避免编写大量的C代码 。
- 缺点/安全问题:
system()必须解释命令字符串,这容易出错 。- 存在命令行注入的安全风险,用户可以通过在输入文本中注入命令行代码(例如使用
&&)来运行任意命令 。 - 其他问题包括注释文本中的引号(撇号)破坏命令引号,以及
PATH变量可能导致调用错误的程序 。
三、 exec() 家族函数
- 功能: 运行其他程序来替换当前的进程 。新程序启动后会沿用原有进程的PID(进程标识符)。它通过明确指定要运行的程序来消除
system()的歧义问题 。 - 进程 (Process): 存储器中运行的程序,由一个数字(进程标识符,PID)来标识 。
- 函数命名规则:
exec后可跟l或v,以及可选的p和e。l(List): 接收参数列表 。v(Vector): 接收参数数组或向量 。p(PATH): 根据PATH环境变量查找程序 。e(Environment): 接收环境变量字符串数组 。- 参数列表函数示例:
execl(),execlp(),execle()。参数列表必须以NULL结束 。 - 参数数组函数示例:
execv(),execvp(),execve()。
- 环境变量 (Environment Variables):
- 每个进程都有一组环境变量 。
- C程序可以使用
stdlib.h中的getenv()函数读取环境变量 。 - 使用以
e结尾的exec()函数(如execle())可以传递新的环境变量数组,数组格式为"变量名=值",并以NULL结束 。
四、 错误处理 (Error Handling)
exec()错误判断: 如果exec()调用成功,当前程序会停止运行;如果失败,程序会继续运行exec()之后的代码 。- 失败黄金法则: 大多数系统调用失败时会遵循以下原则:
- 返回
-1。 - 将全局变量
errno设为错误码 。
- 返回
errno: 定义在errno.h中的全局变量,用于保存系统调用的错误码 。strerror(): 定义在string.h中,可以将errno错误码转换为可读的标准错误消息 。
五、 fork() 函数
- 问题背景: 由于
exec()会替换当前进程,如果在循环中直接调用exec(),程序只会执行一次exec()就会终止 。 - 功能:
fork()系统调用会克隆当前进程,创建一个新的子进程 。父进程和子进程会从fork()调用后的同一行代码开始执行 。 - 返回值区分:
- 子进程 (Child Process):
fork()返回 0 。 - 父进程 (Parent Process):
fork()返回 非零值(即子进程的PID)。 - 失败:
fork()返回 -1 。
- 子进程 (Child Process):
fork()+exec()模式: 在 子进程 中调用exec()函数来替换自身运行新程序,从而允许 父进程 继续运行,实现并发或连续操作 。- 效率技巧: 操作系统使用“写时复制”(copy on-write)技术,父子进程最初共享数据,只有当其中一方尝试修改数据时,系统才会进行复制.
- 平台限制: Windows 系统本身不支持
fork(),但在 Cygwin 等环境中可以通过模拟实现 。
AI对学习内容的总结
一、系统调用与内核机制
核心思想:
系统调用(System Call)是用户程序与内核之间的接口。当 C 程序需要操作硬件(例如读写文件、创建进程等)时,必须通过系统调用进入内核态执行。
要点补充:
-
系统调用通过 CPU 的特权指令(如
syscall或int 0x80)进入内核。 -
用户态与内核态的隔离是操作系统安全与稳定的基础。
-
Linux 系统调用的典型例子:
- 进程管理:
fork()、exec() - 文件 I/O:
open()、read()、write() - 网络通信:
socket()、bind()、connect()
- 进程管理:
二、system() 与 exec() 的区别
| 特性 | system() |
exec() 家族 |
|---|---|---|
| 执行方式 | 通过 shell 解释命令 | 直接执行目标程序 |
| 是否替换当前进程 | 否 | 是 |
| 安全性 | 存在命令注入风险 | 安全性更高 |
| 适用场景 | 快速执行外部命令 | 受控地运行外部程序 |
说明:
system()本质上相当于执行:fork()→exec("/bin/sh", "-c", command)→wait()- 因为
system()需要调用 shell 解释命令字符串,因此容易受输入注入攻击。 - 在安全性要求高的程序中,应使用
fork()+exec()替代。
三、exec() 家族函数详解
exec() 是一组函数族,核心作用是用新程序替换当前进程。
这些函数的命名体现了参数传递和环境处理方式的差异:
| 后缀 | 含义 | 示例 |
|---|---|---|
l |
参数以列表形式给出 | execl() |
v |
参数以数组形式给出 | execv() |
p |
按 PATH 搜索可执行文件 | execlp()、execvp() |
e |
可自定义环境变量 | execle()、execve() |
示例:
execl("/bin/ls", "ls", "-l", NULL);
execvp("ls", argv);
关键点:
- 执行成功后不会返回;失败时返回
-1并设置errno。 - 使用
e系列函数可以改变子进程的环境变量。
四、错误处理机制
通用规则:
大多数系统调用在失败时会:
- 返回
-1; - 设置全局变量
errno以保存错误码。
配合 strerror() 使用示例:
int fd = open("file.txt", O_RDONLY);
if (fd == -1) {
printf("Error: %s\n", strerror(errno));
}
补充理解:
errno定义在<errno.h>中。strerror()定义在<string.h>中,将错误码转为可读文本。- 在多线程环境中,每个线程有独立的
errno。
五、fork():进程创建的基础
fork() 用于创建一个新的子进程。新进程与父进程几乎完全相同,从 fork() 调用处继续执行。
返回值:
- 父进程中:返回子进程的 PID(正数)
- 子进程中:返回 0
- 失败:返回 -1
典型模式:fork() + exec()
pid_t pid = fork();
if (pid == 0) {
execlp("ls", "ls", "-l", NULL);
} else if (pid > 0) {
wait(NULL);
}
补充:
- Linux 使用“写时复制”(Copy-on-Write)优化,只有在数据被修改时才实际复制内存页。
- Windows 没有
fork(),通过CreateProcess()创建新进程。
六、进一步延伸
-
wait()与waitpid()
父进程通过它们等待子进程结束,防止产生僵尸进程。 -
进程标识函数
getpid():获取当前进程 PID。getppid():获取父进程 PID。
-
进程间通信(IPC)机制
pipe()、共享内存、信号量、消息队列等是多进程编程的重要工具。
-
系统调用与库函数的关系
- 系统调用由内核实现;
- C 标准库函数是对系统调用的封装和扩展。
例如:
fopen()→ 调用open()
fread()→ 调用read()
七、学习建议
-
实践练习:
- 使用
fork()创建多个子进程; - 每个子进程调用
exec()执行不同程序; - 父进程使用
wait()等待全部子进程结束。
- 使用
-
对比
system()与exec()的行为差异。 -
阅读 Linux 手册页:
man 2 fork、man 3 exec、man 3 system
学习思维导图
基于AI的学习
原对话链接:https://chatgpt.com/share/6911cc4b-5d54-800b-88ae-dc7ac6ff2d3d
1. 系统调用的核心作用
提问:
系统调用的核心作用是什么?为什么 C 标准库中的很多函数需要依赖系统调用?
回答:
系统调用是C程序与操作系统内核进行通信的机制,是应用程序访问计算机硬件和系统资源(如进程、内存、文件、网络等)的唯一途径。
2. 内核管理三方面
提问:
内核在计算机系统中主要负责哪三方面的管理?你能分别简单说明它们的作用吗?
回答:
处理机、存储、设备
补充说明:
- 内核负责管理 进程 (Processes)、内存 (Memory) 和 硬件设备 (Hardware)。
- 每一项作用:
- 进程管理:加载程序、分配资源、监控状态。
- 内存管理:分配内存空间、使用虚拟内存。
- 硬件管理:通过驱动控制设备。
3. “为什么不是处理机”
提问:
我这里不明白为什么不是处理机,请讲解。
回答说明:
- 内核管理的是 进程的执行,而不是 CPU 硬件本身。
- 内核分配 CPU 时间片给进程,CPU 执行指令,但 CPU 硬件自身不受内核直接控制。
- 所以内核职责是 进程、内存、硬件,不是“处理机”。
4. 系统调用图解
提问:
可以画个图说明吗?
图示:
+---------------------------+
| 用户程序 |
| (进程A、进程B……) |
+---------------------------+
|
| 系统调用 / 进程请求 CPU
v
+---------------------------+
| 内核 |
| - 进程管理(调度、创建、终止) |
| - 内存管理(分配、保护、虚拟内存)|
| - 硬件管理(通过驱动控制设备) |
+---------------------------+
|
| 调度 CPU 执行指令
v
+---------------------------+
| CPU |
| 执行指令,处理数据 |
+---------------------------+
5. system() 函数优缺点
提问:
system() 函数可以用来在 C 程序中运行其他程序。你觉得它有哪些优点和潜在的安全风险?
回答:
优点:简便
缺点:shellcode注入风险
补充说明:
system()调用 shell 执行命令,易受命令行注入攻击。- 示例:
char cmd[256];
snprintf(cmd, sizeof cmd, "ls %s", user_input);
system(cmd); // 不安全,用户输入可包含 ; && 等
- 安全替代:使用
fork() + exec*()或posix_spawn(),直接传参数数组,不通过 shell。
6. fork() 返回值
提问:
fork() 调用后如何通过返回值区分父进程和子进程?
回答:
- 0 → 子进程
- 非零正数 → 父进程(子进程 PID)
- -1 → 创建失败
7. fork() 与 exec() 的配合
提问:
为什么我们常把 fork() 和 exec() 配合使用,而不是直接在父进程中调用 exec()?
回答:
exec()会替换当前进程,如果直接在父进程调用,父进程消失,后续代码不再执行。- 先
fork()创建子进程,子进程执行exec(),父进程继续运行,实现并发或连续操作。
8. 对话总结
回答总结:
- 系统调用理解正确。
- 内核管理三方面:进程、内存、硬件,概念比“处理机”更准确。
system()简便但易受命令注入攻击,推荐使用fork() + exec*()。fork()返回值理解正确,配合exec()实现子进程执行新程序而父进程继续运行。
建议:
- 精确使用术语(进程、内存、硬件)。
- 写实验程序观察
fork()+exec()的执行。 - 绘制内核与 CPU、父子进程流程图帮助记忆和理解。
学习实践过程遇到的问题与解决方式
1. Windows 与类 Unix fork() 模型设计理念差异及优势
fork() 系统调用是许多类Unix系统的核心功能,但Windows原生不支持,需要Cygwin等工具进行模拟。你认为 Windows 采用的与类 Unix 系统完全不同的进程管理方式,在设计理念上有什么核心优势(或者说,它解决了类 Unix fork() 模型可能存在的哪些问题)?
类 Unix 系统(Linux、macOS 等):
-
fork()是核心进程创建机制:子进程是父进程的完整复制,包括内存空间、打开的文件描述符等。 -
优点:
- 很自然的并发模型,父子进程几乎完全相同。
- 与
exec()结合,可以非常灵活地在子进程中替换程序,形成多进程服务。
-
缺点:
- 资源开销大:尤其是内存复制,虽然现代操作系统使用“写时复制”优化,但在大内存进程或频繁创建进程时仍有开销。
- 复杂的父子状态管理:子进程继承大量状态,可能导致资源冲突或管理难度增加。
Windows 进程模型:
-
Windows 不使用
fork(),而是通过CreateProcess()直接创建一个新进程,并可指定初始程序和参数。 -
核心优势:
-
更轻量的进程创建
- 不复制整个父进程内存空间,避免“写时复制”也可能带来的性能损耗。
-
更明确的资源边界
- 新进程可以指定只继承需要的句柄,不会无意中继承大量父进程状态。
-
安全性更高
- 避免父子进程共享过多内部状态,减少潜在的冲突和隐式依赖。
-
适合多语言和多环境
- Windows 的 API 直接支持指定可执行文件和启动参数,简化跨语言或跨程序调用。
-
-
总结:Windows 的设计理念是明确控制进程边界和资源继承,避免
fork()模型可能引发的资源开销和复杂继承问题。
2. fork() 缺失对 C 程序开发或部署的影响
fork() 在Windows上的缺失是否限制了某些特定类型C程序的开发或部署?
-
对一般 C 程序影响有限:
- 许多 C 程序只依赖标准库和线程,或者使用
system()/CreateProcess()执行外部命令,几乎不依赖fork()。
- 许多 C 程序只依赖标准库和线程,或者使用
-
对某些类型程序有限制:
-
Unix 风格的多进程服务
- 例如 Web 服务器或守护进程利用
fork()快速创建子进程处理请求。 - 在 Windows 上必须改写为线程模型或使用
CreateProcess(),增加移植工作。
- 例如 Web 服务器或守护进程利用
-
某些工具和脚本
- 使用
fork()+exec()的工具链(如某些 shell 脚本、管道处理程序)无法直接在 Windows 上运行。
- 使用
-
教学或实验程序
- 在操作系统实验、并发控制练习中,无法直接用
fork()演示父子进程的行为。
- 在操作系统实验、并发控制练习中,无法直接用
-
-
解决方式:
- 使用 Cygwin 或 WSL 模拟
fork()。 - 使用 Windows 原生 线程、CreateProcess()、管道机制 重构程序。
- 使用 Cygwin 或 WSL 模拟
本文链接
参考资料
- AI工具(你使用的AI工具及其链接)
- 图书

浙公网安备 33010602011771号