KyrieChao

2026-10-24 周规划

2026-10-24约 6 分钟

本周重点

你说得对。之前的计划只有知识点,没有明确的练习题、代码产出和验收标准,而且没有给当天的新课留出位置。

下周应该采用“双线推进”:当天的新课正常学习,旧知识每天安排一个具体的编程任务。 不要求六天内精通 Day1–Day10,而是让每一天都有可以验证的结果。

10 月 26 日—31 日计划

先确定每天的时间分配

考虑到你培训时间较长,我建议按以下结构安排:

第一条线:当天的新知识

正常跟进培训课程。课后用 10–15 分钟记录新概念、关键 API、尚未解决的问题;有代码示例就自己复现一个最小版本。

第二条线:旧知识实战

工作日安排约 30–45 分钟,周六可适当延长。先独立完成题目,再查笔记、编译、运行和排错。

以下是旧知识实战的具体安排。当天新课的主题以老师实际讲授内容为准,不预先虚构课程进度。如果当天有必须完成的培训作业,优先完成作业,再适当缩减旧知识练习。

10 月 26 日,周一:Day 1 文件操作

核心任务:独立实现文件复制程序

具体练习

编写 mycp.c,实现以下命令:

./mycp source.txt destination.txt

要求:

  1. 使用 open() 打开源文件、创建目标文件。
  2. 使用固定大小的缓冲区,例如 char buf[1024]。
  3. 使用循环调用 read(),直到文件读取结束。
  4. 对每次读取到的数据使用 write() 写入目标文件。
  5. 正确处理文件描述符关闭和错误返回。

必须测试的情况:

  • 源文件为空。
  • 源文件小于 1024 字节。
  • 源文件大于 1024 字节。
  • 源文件不存在。
  • 目标文件已存在。

达到什么程度才算完成?

你要能解释 read() 的三种返回情况:-1 表示出错、0 表示到达 EOF、正数表示实际读取的字节数。

同时必须理解:write() 不保证一次写完所有请求的数据,因此要考虑短写;不能把一次 read() 当作读取整个文件。缓冲区不需要随着文件大小增长,循环分块处理即可。

时间充足后,再补充 lseek() 和 stat():写一个小程序,输出文件大小,并通过 lseek() 查看文件偏移。

10 月 27 日,周二:Day 2 库

核心任务:制作并使用静态库和共享库

建立一个简单项目:

library_demo/
├── calc.h
├── calc.c
└── main.c

calc.c 实现 add() 和 sub(),main.c 调用它们。

必须完成:

  1. 编译出 libcalc.a。
  2. 使用静态库链接生成可执行文件,并运行。
  3. 编译出 libcalc.so。
  4. 使用共享库链接生成另一个可执行文件,并运行。
  5. 将共享库放到单独的目录,使用 LD_LIBRARY_PATH 测试运行时搜索。

验收标准

你能不照抄命令,独立完成下面的流程:

calc.c → calc.o → libcalc.a → 静态链接程序
calc.c → calc.o → libcalc.so → 动态链接程序

并能解释 -I、-L、-l、-fPIC、-shared 分别解决什么问题,以及为什么共享库链接成功后仍可能在运行时找不到。

这一天不要求掌握 dlopen() 插件机制,只需能说明它的用途。

10 月 28 日,周三:Day 3 进程

核心任务:实现 fork、waitpid、exec 的组合程序

编写 process_demo.c:

  1. 父进程使用 fork() 创建子进程。
  2. 子进程打印自己的 PID 和 PPID,然后以状态码 7 退出。
  3. 父进程使用 waitpid() 等待子进程。
  4. 父进程使用 WIFEXITED() 和 WEXITSTATUS() 获取并打印子进程退出状态。
  5. 再编写一个独立小例子,让子进程用 execl() 执行 /bin/ls,观察程序替换后的执行过程。

验收标准

不看笔记,你需要能解释:

  • fork() 为什么在父子进程中返回不同的值。
  • fork() 后父子进程分别从哪里继续执行。
  • exec 成功后,原程序的代码为什么不再继续执行。
  • waitpid() 的作用,以及退出状态 7 如何被取出。
  • 为什么子进程退出后,父进程不及时回收可能产生僵尸进程。

vfork()、进程组、会话和守护进程这一天只做概念复述,不要求同时实现。

10 月 29 日,周四:Day 4–5 进程间通信

核心任务:真正跑通一次父子进程通信

第一项:匿名管道程序

使用 pipe() 和 fork(),实现:

父进程
  │ write("hello child")
  ▼
匿名管道
  │ read()
  ▼
子进程
  └── 打印收到的内容

要求正确关闭父子进程不需要的管道端,并验证关闭所有写端后,读端最终会读到 EOF。

第二项:信号小练习

编写一个简单的 SIGALRM 示例,观察定时信号的触发。理解信号的默认处理、自定义处理以及 pause() 的用途。信号处理函数中不要直接进行不安全的复杂操作,例如直接调用普通的 printf()。

第三项:IPC 口述验收

不强求一天之内再实现完整的共享内存和消息队列程序,但以下问题必须答得出来:

  • 管道、共享内存、消息队列、信号分别适合什么情形?
  • 为什么共享内存通常还需要同步机制?
  • shmdt() 和 shmctl() 的作用有什么区别?
  • 孤儿进程和僵尸进程的区别是什么?

达到什么程度?

管道程序必须能够编译运行;其余知识能够结合调用流程解释清楚。如果共享内存或消息队列的调用顺序还不熟悉,就记为下一轮待练项目,不用假装已经掌握。

10 月 30 日,周五:Day 6–8 线程与同步

核心任务:复现竞态条件,并用锁修复

这一天不实现完整的线程池。先把共享数据保护做扎实,再处理高级同步。

第一项:竞态条件

创建多个线程,让它们共同对一个全局计数器执行递增操作,例如 4 个线程各自递增 100,000 次。

先不加锁,运行观察最终结果;再用 pthread_mutex_t 保护递增操作,重新运行。

验收要求:理解为什么 counter++ 不能简单视为一个不可分割的原子操作,以及为什么加锁后结果可以符合预期。

第二项:条件变量

写出一个最小的生产者—消费者示例,或先补齐其关键代码:

  • 共享数据由互斥锁保护。
  • 条件不满足时使用 pthread_cond_wait()。
  • 等待条件用 while 检查,而不是只用 if。
  • 条件满足后由生产者或消费者发出合适的通知。

第三项:概念测试

能区分互斥锁、信号量和条件变量的主要用途;能说出死锁的四个必要条件;能够说明 Joinable 和 Detached 线程的资源回收方式。

线程池这一天只要求画出“任务队列—工作线程—同步机制”的结构,不实现完整线程池。

10 月 31 日,周六:Day 9–10 网络通信

核心任务:独立实现 TCP Echo Client/Server

这也是下周最值得重视的一天,因为它和你正在学习的网络通信课程、后续工业设备通信项目直接相关。

第一项:服务端

编写一个服务端程序,实现:

socket()
   ↓
bind()
   ↓
listen()
   ↓
accept()
   ↓
recv() / send()
   ↓
close(conn_fd)

服务端先只支持一次处理一个客户端,不要求多线程或高并发。

第二项:客户端

编写对应客户端,使用 socket() 和 connect() 建立连接,然后发送文本、接收回显并正确关闭套接字。

第三项:故障测试

至少进行以下测试:

  • 客户端发送正常文本,服务端正确回显。
  • 客户端退出,服务端识别 recv() 返回 0。
  • 客户端尝试连接错误的端口,观察并分析错误。
  • 修改端口配置,练习 htons() 和 setsockopt() 的使用场景。

验收标准

你要能解释 listen_fd 和 conn_fd 的区别,说明 TCP 是字节流而非消息协议,以及为什么 send() 和 recv() 的一次调用不一定对应一次完整的应用层消息。

第一版不要求处理复杂协议分帧,也不要求实现高并发。先把连接建立、数据收发、连接关闭和基本错误检查写正确。

最后,怎么判断这六天有没有效果?

别用“看过了”“好像明白了”作为完成标准。每天给自己记录三个状态:

状态 判断条件
A:独立完成 不看答案,可以写出主要代码并编译运行
B:借助资料完成 查看 API 文档或笔记后能够修正并跑通
C:尚未掌握 需要参考完整答案,或无法解释关键执行过程

每个任务都可以标记为 A、B 或 C。标记 C 不意味着当天失败,而是明确告诉你下一轮练什么。

我的判断是:下周不应该以“复习完十天”作为目标,而应该以至少完成四个可以运行的程序为底线:文件复制、库调用、进程控制、TCP 通信;并额外完成一次线程竞态与锁的验证。 IPC 其余机制和高级同步则先达到能解释、能定位、知道如何调用的程度。

这样既没有把你每天的新课挤掉,也能让旧知识真正进入编程实践。