第一章_UINX基础知识_《UNIX 环境高级编程(第三版)》_笔记

《UNIX环境高级编程(第三版)》第一章重难点详解

一、章节核心定位

第一章是全书的"入门地基",核心是帮读者建立UNIX系统的底层认知框架------从"内核如何提供服务"到"应用如何调用接口",所有重难点都围绕"理论→接口→代码实践"展开。本章核心重难点共5个:UNIX体系结构与系统调用文件与目录操作(万物皆文件)文件描述符与I/O模型进程控制基础(fork/exec/waitpid)信号与出错处理。每个重难点都结合书中核心代码,分步拆解原理与实操。

二、重难点1:UNIX体系结构(理解"内核-库-应用"的协作逻辑)

1.1 核心概念:四层模型与关键区别

UNIX系统从底层到上层分为 内核→公用函数库→shell→应用程序,核心是搞懂"系统调用"和"库函数"的本质区别:

  • 内核:系统核心,控制硬件(CPU/内存/磁盘),提供"系统调用接口"(如open/read),是应用访问硬件的唯一入口。
  • 公用函数库:基于系统调用封装(如printf底层调用write),简化编程,减少重复开发。
  • shell:特殊应用(如bash),解析用户命令,通过fork/exec启动其他程序,是用户与系统的"中介"。

1.2 关键代码:无缓冲I/O的内核直接交互(对应图1-4)

代码功能:从标准输入复制数据到标准输出,直接调用内核提供的无缓冲I/O函数,直观体现"应用→内核"的交互流程。

c 复制代码
#include "apue.h"  // 书中自定义头文件:包含标准头文件+错误处理函数
#define BUFFSIZE 4096  // 缓冲区大小:匹配磁盘块大小(4096字节),优化I/O效率

int main(void) {
    int n;
    char buf[BUFFSIZE];  // 自定义缓冲区:存储读取的数据

    // 循环读取标准输入(STDIN_FILENO=0),写入标准输出(STDOUT_FILENO=1)
    while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) {
        // 若写入字节数≠读取字节数,说明出错
        if (write(STDOUT_FILENO, buf, n) != n)
            err_sys("write error");  // 自定义错误处理:打印系统错误信息
    }

    // 若read返回负值,说明读取出错
    if (n < 0)
        err_sys("read error");

    exit(0);  // 正常终止进程:内核自动关闭所有打开的文件描述符
}

1.3 分步拆解代码(核心难点突破)

步骤1:头文件与缓冲区定义
  • apue.h:书中自定义头文件,封装了<unistd.h>(系统调用头文件)、<stdio.h>等标准头文件,还定义了err_sys等错误处理函数(避免重复写perror)。
  • BUFFSIZE=4096:关键优化点!多数UNIX文件系统的磁盘块大小是4096字节,设置缓冲区为该值可减少read/write的调用次数(系统调用需切换用户态→内核态,成本高)。若设为1字节,会触发百万次系统调用,效率极低。
步骤2:文件描述符的默认值
  • STDIN_FILENO(0)、STDOUT_FILENO(1):POSIX标准定义的符号常量(在<unistd.h>中),替代硬编码0/1,提升可读性和可移植性。
  • 为什么不用0/1直接写?:若后续代码修改了文件描述符分配,硬编码会出错,符号常量可避免此问题。
步骤3:read/write函数的核心逻辑
  • read函数原型:ssize_t read(int fd, void *buf, size_t nbytes)
    • 返回值:>0表示实际读取字节数;=0表示文件尾端;<0表示出错。
    • 循环条件n > 0:只要还有数据可读,就持续读取,直到文件尾端。
  • write函数原型:ssize_t write(int fd, const void *buf, size_t nbytes)
    • 必须检查返回值:若返回值≠n(如磁盘满),说明写入不完整,需处理错误。
步骤4:错误处理函数err_sys
  • 作用:封装perrorexit,打印"错误信息+系统错误描述"(如"write error: No space left on device"),简化错误处理代码。
  • 书中所有实例均使用此类自定义函数,后续章节会统一讲解(附录B有实现)。

1.4 重难点总结

  • 核心难点:理解"无缓冲I/O"的本质------每次read/write都直接调用内核,缓冲区由用户手动管理。
  • 关键结论:缓冲区大小需匹配磁盘块大小(4096字节是通用最优值),减少系统调用次数是UNIX I/O性能优化的核心思路。

三、重难点2:文件与目录(理解"万物皆文件"与路径解析)

2.1 核心概念:文件系统与路径规则

  • 层次结构:以根目录(/)为起点,目录是"包含目录项的特殊文件",每个目录项含"文件名+指向i节点的指针"(i节点存储文件属性:权限、大小等)。
  • 路径规则:
    • 绝对路径:以/开头(如/etc/passwd),从根目录解析。
    • 相对路径:不以/开头(如doc/memo),从当前工作目录解析。
    • 特殊目录项:.=当前目录,..=父目录(根目录的..等于.)。
  • 文件名限制:仅/和空字符不可用,POSIX推荐用字母、数字、.-_(保证可移植性)。

2.2 关键代码:列出目录内容(对应图1-3)

代码功能:模拟ls命令核心逻辑,读取指定目录的所有文件名,体现"目录作为特殊文件"的操作规则。

c 复制代码
#include "apue.h"
#include <dirent.h>  // 目录操作专用头文件:包含opendir/readdir/closedir原型

int main(int argc, char *argv[]) {
    DIR *dp;                // 目录流指针:类似FILE,内核维护目录读取状态
    struct dirent *dirp;    // 存储单个目录项信息:含文件名d_name、i节点号d_ino

    // 步骤1:检查命令行参数(需传入1个目录名,如./a.out /etc)
    if (argc != 2)
        err_quit("usage: ls directory_name");  // 无系统错误,仅打印用法并退出

    // 步骤2:打开目录:返回DIR指针,失败则报错(如权限不足、不是目录)
    if ((dp = opendir(argv[1])) == NULL)
        err_sys("can't open %s", argv[1]);

    // 步骤3:循环读取目录项:readdir返回下一个目录项,NULL表示目录尾端或出错
    while ((dirp = readdir(dp)) != NULL)
        printf("%s\n", dirp->d_name);  // 打印目录项中的文件名

    // 步骤4:关闭目录流,释放内核资源
    closedir(dp);
    exit(0);
}

2.3 分步拆解代码(核心难点突破)

步骤1:目录操作函数族的特殊性
  • 为什么不能用read直接读目录?:目录是内核保护的特殊文件,直接写/读会破坏目录结构,必须用opendir/readdir/closedir专用函数。
  • 函数分工:
    • opendir:打开目录,初始化"目录读取位置"(从起始处开始),返回DIR指针(类似open返回文件描述符)。
    • readdir:读取下一个目录项,填充struct dirent,关键成员d_name(文件名,以\0结尾)。
    • closedir:关闭目录流,释放内核资源(类似close关闭文件描述符)。
步骤2:错误场景处理
  • 场景1:传入路径不是目录(如./a.out /dev/tty)→ opendir返回NULLerr_sys打印"can't open /dev/tty: Not a directory"。
  • 场景2:无权限打开目录(如./a.out /etc/ssh/private)→ opendir返回NULLerr_sys打印"can't open /etc/ssh/private: Permission denied"。
  • err_quiterr_sys的区别:err_quit仅打印用法,不涉及系统错误;err_sys打印系统错误描述(依赖errno)。
步骤3:目录项的排序问题
  • 代码输出的文件名不是字母顺序:readdir按目录在磁盘上的存储顺序读取,而ls命令会额外排序,这是ls的扩展功能,核心目录读取逻辑就是本代码的逻辑。

2.4 重难点总结

  • 核心难点:理解"目录是特殊文件"------仅内核可修改,用户只能通过专用函数读取,且目录项不存储文件属性(属性存在i节点中,通过stat函数获取)。
  • 关键结论:路径解析的核心是"绝对路径从根开始,相对路径从当前目录开始",...是目录的默认项,保证路径解析的灵活性。

四、重难点3:进程与进程ID(理解"程序→进程"的转换)

3.1 核心概念:程序与进程的区别

  • 程序:磁盘上的可执行文件(如/bin/ls),静态存在。
  • 进程:程序的"执行实例"(如运行ls时创建的进程),动态存在,一个程序可对应多个进程(如多次运行./a.out)。
  • 进程ID(PID):每个进程的唯一非负整数标识,由内核分配,getpid()获取当前进程PID,fork()创建子进程时,父进程获子PID,子进程获0。

3.2 关键代码:打印进程ID(对应图1-6)

代码功能:获取并打印当前进程PID,理解PID的唯一性和数据类型强制转换的必要性。

c 复制代码
#include "apue.h"

int main(void) {
    // 打印进程ID:getpid()返回pid_t类型,强制转换为long保证可移植性
    printf("hello world from process ID %ld\n", (long)getpid());
    exit(0);
}

3.3 分步拆解代码(核心难点突破)

步骤1:pid_t数据类型与强制转换
  • pid_t:UNIX系统定义的基本数据类型(在<sys/types.h>中),长度随系统变化(32位/64位)。
  • 为什么强制转换为long?:printf%ld格式符要求参数为long类型,若pid_t长度小于long(如32位系统),不转换会导致格式不匹配,打印错误。这是UNIX编程"可移植性"的关键细节。
步骤2:运行结果分析
bash 复制代码
$ ./a.out
hello world from process ID 851  # 第一次运行的PID
$ ./a.out
hello world from process ID 854  # 第二次运行的PID(不同)
  • PID唯一性:每次运行程序创建新进程,内核分配新PID,0通常为内核进程,用户进程PID从1开始(init进程)。

3.4 重难点总结

  • 核心难点:pid_t的可移植性处理------不能直接用int接收,必须强制转换为long或用%zd(C99),避免跨平台兼容问题。
  • 关键结论:进程是程序的动态执行实例,PID是其唯一标识,后续进程控制(fork/exec)均依赖PID。

五、重难点4:进程控制基础(fork+exec+waitpid协作)

4.1 核心概念:进程控制三要素

  • fork():创建子进程,复制父进程地址空间(代码、数据、堆栈),但共享打开的文件描述符,"一次调用,两次返回"(父进程返子PID,子进程返0)。
  • exec():替换当前进程映像(如将./a.out替换为/bin/ls),成功则不返回,失败才返回-1,需与fork配合实现"创建子进程→执行新程序"。
  • waitpid():父进程等待子进程终止,获取子进程终止状态(正常/信号终止),避免"僵尸进程",options=0表示阻塞等待。

4.2 关键代码:简易shell(对应图1-7)

代码功能:模拟shell核心逻辑------读取用户命令,创建子进程执行命令,父进程等待子进程终止,体现fork+exec+waitpid的协作。

c 复制代码
#include "apue.h"
#include <sys/wait.h>  // 包含waitpid()原型

int main(void) {
    char buf[MAXLINE];  // 存储用户输入命令(MAXLINE在apue.h中定义为1024)
    pid_t pid;          // 存储子进程PID
    int status;         // 存储子进程终止状态

    printf("%% ");  // 打印shell提示符(%%转义为%)
    // 步骤1:读取用户输入:fgets从标准输入读一行,NULL表示文件尾端(Ctrl+D)
    while (fgets(buf, MAXLINE, stdin) != NULL) {
        // 步骤2:去除换行符:fgets读取的行以\n结尾,替换为\0(exec要求命令以\0结尾)
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';

        // 步骤3:创建子进程
        if ((pid = fork()) < 0) {
            err_sys("fork error");  // fork失败(如系统资源不足)
        } else if (pid == 0) {      // 子进程:执行用户命令
            // execlp:从PATH环境变量搜索命令,参数格式:(命令名, 命令名, NULL)
            execlp(buf, buf, (char *)0);
            // 若execlp返回,说明执行失败(如命令不存在)
            err_ret("couldn't execute: %s", buf);
            exit(127);  // 退出子进程,返回错误状态码(127=shell约定"命令未找到")
        }

        // 步骤4:父进程:等待子进程终止
        if ((pid = waitpid(pid, &status, 0)) < 0)
            err_sys("waitpid error");

        printf("%% ");  // 打印下一个提示符
    }
    exit(0);
}

4.3 分步拆解代码(核心难点突破)

步骤1:fork的"一次调用,两次返回"
  • 父进程:fork()返回子PID(>0),进入"等待子进程"逻辑。
  • 子进程:fork()返回0,进入"执行命令"逻辑,调用execlp替换自身映像。
  • 关键注意:子进程执行execlp成功后,原代码(err_ret/exit)不会执行;只有execlp失败才会执行后续代码------这是exec的"替换特性"。
步骤2:execlp函数的参数规则
  • 原型:int execlp(const char *file, const char *arg, ..., (char *)0)
  • 第一个参数:命令名(如date),execlp会从PATH环境变量(如/bin:/usr/bin)搜索可执行文件路径(如/bin/date)。
  • 可变参数:从第二个参数开始是"命令参数列表",必须以(char *)0结尾(标记参数结束),例如执行ls -l需传入execlp("ls", "ls", "-l", (char *)0)
步骤3:waitpid的作用
  • 阻塞等待:options=0表示父进程暂停执行,直到子进程终止。
  • 终止状态:status参数存储子进程终止原因(如正常退出码、被信号终止的信号码),后续第8章会详解解析方法。
步骤4:运行结果分析
bash 复制代码
$ ./a.out
% date  # 用户输入命令
Sat Jan 21 19:42:07 EST 2012  # 子进程执行date的输出
% who  # 用户输入命令
sar console Jan 11 14:59  # 子进程执行who的输出
% ^D  # 按Ctrl+D,fgets返回NULL,程序退出
  • 限制:该程序暂不支持命令参数(如ls -l会报错),需后续扩展参数解析逻辑(分割buf中的命令与参数)。

4.4 重难点总结

  • 核心难点:fork的地址空间复制(写时复制优化)、exec的进程映像替换、waitpid的僵尸进程预防------三者协作是UNIX进程控制的核心。
  • 关键结论:shell的本质就是"读取命令→fork子进程→exec执行命令→waitpid等待"的循环,本代码是shell的最小实现。

六、重难点5:信号与出错处理(异步通知与统一错误接口)

5.1 核心概念:信号与errno规则

  • 信号:内核向进程发送的"异步事件通知"(如SIGINT=Ctrl+C终止进程、SIGFPE=浮点异常),进程可"忽略""默认处理"或"捕捉"(注册自定义函数)。
  • errno规则:
    1. 系统函数出错时,设置errno为特定值(如EACCES=权限不足、ENOENT=文件不存在)。
    2. 成功时errno不清除,仅当函数返回错误时,errno才有效。
    3. 所有errno常量≠0,strerror(errno)可转换为可读错误信息。

5.2 关键代码1:信号捕捉(对应图1-10)

在"简易shell"基础上添加信号捕捉,使按下Ctrl+C(SIGINT)时不终止程序,仅打印提示。

c 复制代码
#include "apue.h"
#include <sys/wait.h>

// 步骤1:定义信号捕捉函数:SIGINT信号发生时调用
static void sig_int(int signo) {
    printf("\ninterrupt\n");  // 打印提示
    printf("%% ");             // 重新打印提示符
    fflush(stdout);            // 冲洗标准输出(行缓冲需手动冲洗,避免提示不显示)
}

int main(void) {
    char buf[MAXLINE];
    pid_t pid;
    int status;

    // 步骤2:注册信号捕捉函数:捕捉SIGINT,失败则报错
    if (signal(SIGINT, sig_int) == SIG_ERR)
        err_sys("signal error");

    printf("%% ");
    while (fgets(buf, MAXLINE, stdin) != NULL) {
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';

        if ((pid = fork()) < 0) {
            err_sys("fork error");
        } else if (pid == 0) {
            execlp(buf, buf, (char *)0);
            err_ret("couldn't execute: %s", buf);
            exit(127);
        }

        if ((pid = waitpid(pid, &status, 0)) < 0)
            err_sys("waitpid error");

        printf("%% ");
        fflush(stdout);  // 冲洗提示符
    }
    exit(0);
}
代码拆解(核心难点)
  • signal()函数:原型void (*signal(int signo, void (*func)(int)))(int),作用是"为signo信号注册func处理函数"。
  • 行缓冲问题:标准输出默认是行缓冲,若无换行符(如printf("%% ")),缓冲不会自动冲洗,需手动调用fflush(stdout)确保提示显示。
  • 捕捉效果:未添加捕捉时,Ctrl+C终止程序;添加后,程序打印interrupt并继续运行,体现"信号的可定制性"。

5.3 关键代码2:出错信息打印(对应图1-8)

代码功能:演示strerrorperror的使用,理解errno与出错信息的映射。

c 复制代码
#include "apue.h"
#include <errno.h>  // 包含errno定义与出错常量

int main(int argc, char *argv[]) {
    // 1. strerror:将errno值(此处直接传EACCES)转换为出错信息字符串
    fprintf(stderr, "EACCES: %s\n", strerror(EACCES));

    // 2. perror:基于当前errno值,打印"msg: 出错信息"
    errno = ENOENT;  // 手动设置errno为ENOENT(文件不存在)
    perror(argv[0]);  // argv[0]是程序名(如./a.out),符合UNIX惯例

    exit(0);
}
代码拆解(核心难点)
  • strerror():接收errno值,返回指向错误信息的指针(如"Permission denied"),需用fprintf打印到标准错误(stderr)。
  • perror():先打印msg+": ",再打印errno对应的错误信息+换行符,无需手动传errno
  • 标准错误(stderr):与标准输出(stdout)分离,即使stdout被重定向(如./a.out > log),出错信息仍显示在终端,避免错误信息丢失。

5.4 重难点总结

  • 核心难点:信号的异步性(捕捉函数需可重入)、errno的有效性规则、行缓冲的冲洗问题。
  • 关键结论:UNIX出错处理的核心是"统一接口"------用strerror/perror解析errno,用自定义错误函数(err_sys/err_quit)简化代码,提高可读性。

七、章节总结与实践建议

1. 核心知识框架

第一章的所有重难点都围绕"如何与UNIX内核交互"展开,核心逻辑链:
体系结构(四层模型)→ 接口(系统调用/库函数)→ 实践(文件I/O/进程控制/信号)

2. 必做实践

  1. 编译运行所有代码:需先安装apue库(书中源码可从www.apuebook.com下载),编译命令如gcc -o mycopy mycopy.c -lapue
  2. 修改代码测试:
    • 更改BUFFSIZE为1、1024、8192,观察I/O效率差异(用time ./mycopy < infile > outfile计时)。
    • 在简易shell中添加参数解析,支持ls -l等带参数的命令。
  3. 调试错误场景:故意传入错误路径(如./myls /dev/tty),观察错误处理函数的输出。

3. 后续衔接

  • 第2章(UNIX标准):解释"可移植性"的底层逻辑,呼应本章的long强制转换、sysconf获取系统限制。
  • 第3章(文件I/O):深入open/lseek/dup等函数,扩展本章无缓冲I/O的细节。
  • 第8章(进程控制):详解fork的写时复制、僵尸进程处理,扩展本章简易shell的功能。

《UNIX环境高级编程(第三版)》第一章 中等难度多选题(5题)

题目1:关于UNIX体系结构与系统调用、库函数的区别,下列说法正确的有( )

A. UNIX体系结构从底层到上层依次为:内核→公用函数库→shell→应用程序

B. 系统调用是内核提供的底层接口,执行时需切换用户态与内核态,是应用访问硬件的唯一合法入口

C. 所有库函数均依赖系统调用实现,例如strcpy底层会调用write系统调用

D. 应用程序可直接调用系统调用(如read/write),无需通过库函数中转

E. 库函数通过缓冲机制减少系统调用次数,因此效率通常低于系统调用

题目2:下列关于UNIX文件描述符与无缓冲I/O的说法,正确的有( )

A. 标准输入、标准输出、标准错误的文件描述符常量分别为STDIN_FILENO(0)、STDOUT_FILENO(1)、STDERR_FILENO(2),定义于<unistd.h>

B. read函数返回值为0表示读取出错,返回值为-1表示到达文件尾端

C. 无缓冲I/O的核心特征是"无用户态缓冲",每次read/write调用均直接触发内核系统调用

D. 缓冲区大小BUFFSIZE设为4096字节(多数UNIX磁盘块大小)可减少系统调用次数,优化I/O效率

E. 若write函数返回值小于传入的字节数,说明发生致命错误,无法通过重试恢复

题目3:关于fork函数创建子进程的特性,下列说法正确的有( )

A. fork调用一次,返回两次:父进程返回子进程PID(正整数),子进程返回0,出错返回-1

B. 子进程会完全复制父进程的地址空间(代码、数据、堆栈),且不共享任何资源

C. 子进程调用exec函数族后,进程映像会被新程序替换,原fork后的代码段不再执行

D. 父进程若未调用wait/waitpid等待子进程终止,子进程终止后会成为僵尸进程,占用PID资源

E. 父子进程的执行顺序由父进程决定,父进程一定先于子进程执行

题目4:关于UNIX信号与出错处理的描述,下列说法正确的有( )

A. 信号是内核向进程发送的异步事件通知,例如SIGINT由Ctrl+C触发,SIGFPE由浮点异常触发

B. 进程对信号的处理方式包括"忽略信号""默认处理""捕捉信号(注册自定义函数)"三类,其中硬件异常信号(如SIGFPE)建议忽略以避免程序崩溃

C. signal函数用于注册信号捕捉函数,若注册失败返回SIG_ERR,需结合err_sys函数处理错误

D. 自定义信号处理函数中使用printf打印无换行符的提示时,需手动调用fflush(stdout),因标准输出默认是行缓冲

E. errno变量在函数成功执行时会自动清零,因此可通过errno是否为0判断函数是否出错

题目5:关于UNIX文件系统的路径名与目录规则,下列说法正确的有( )

A. 绝对路径以"/"开头,从根目录解析;相对路径不以"/"开头,从当前工作目录解析,例如".../doc/memo"是相对路径

B. 文件名可包含任意字符,仅禁止"/"(路径分隔符)和空字符(\0),POSIX推荐使用字母、数字、".""-""_"以保证可移植性

C. 读取目录内容可直接使用read函数,无需调用opendir/readdir/closedir专用函数

D. 目录中的"."表示当前目录,"..."表示父目录,根目录(/)的"..."仍指向根目录自身

E. 路径名"/etc/passwd"是相对路径,"./test"是绝对路径


题目详解

题目1 正确答案:ABD

  • A正确:第一章1.2节明确UNIX四层体系结构的核心层级关系,内核提供基础服务,公用函数库简化编程,shell是命令解释器,应用程序基于上层接口实现功能(对应图1-1)。
  • B正确:系统调用是内核暴露的底层接口,直接操控硬件资源,符合"应用→内核"的交互逻辑,是应用访问硬件的唯一合法路径,执行时需切换用户态与内核态。
  • C错误 :并非所有库函数都依赖系统调用,例如纯内存操作的strcpy(字符串复制)、atoi(字符串转整数)等,无需调用内核服务,仅在用户态完成。
  • D正确 :第一章1.5节的无缓冲I/O示例(图1-4)直接调用read/write系统调用,未依赖任何库函数,证明应用可直接使用系统调用。
  • E错误:系统调用因需切换用户态与内核态,存在上下文切换开销,效率低于库函数;库函数通过缓冲减少系统调用次数,执行效率更高。

题目2 正确答案:ACD

  • A正确 :第一章1.5节明确三个标准文件描述符的常量及数值(0、1、2),定义于<unistd.h>头文件,替代硬编码以提升可移植性。
  • B错误 :第一章1.5节说明read返回值规则:>0为实际读取字节数,=0为文件尾端,<0为出错(需结合errno判断错误类型)。
  • C正确:无缓冲I/O的定义即"不设用户态缓冲",每次I/O操作直接调用内核接口,触发系统调用,与标准I/O的缓冲机制形成对比。
  • D正确 :第一章1.5节提到,BUFFSIZE匹配磁盘块大小(4096字节)可减少系统调用次数(系统调用切换成本高),是图1-4程序的核心优化思路。
  • E错误write返回值小于字节数可能是临时错误(如磁盘暂时忙碌),并非致命错误,可通过重试恢复;仅返回-1时表示明确出错。

题目3 正确答案:ACD

  • A正确 :第一章1.6节明确fork的核心特性"一次调用,两次返回",通过返回值区分父子进程,出错时返回-1(如系统资源不足)。
  • B错误:子进程复制父进程地址空间(采用写时复制优化,仅修改时实际复制),但共享打开的文件描述符、信号掩码等资源,并非完全独立。
  • C正确exec函数族会替换进程的完整映像(代码、数据、堆栈),成功后原fork后的代码段被覆盖,不再执行;仅当exec失败时才会执行后续错误处理代码。
  • D正确:第一章1.6节提到,僵尸进程的产生原因是子进程终止后,父进程未回收其终止状态,内核会保留子进程的PID和终止信息。
  • E错误:父子进程是独立的执行流,执行顺序由内核调度算法决定,无固定顺序,无法确保父进程先执行。

题目4 正确答案:ACD

  • A正确 :第一章1.9节定义信号为"异步事件通知",SIGINT(中断信号)和SIGFPE(浮点异常信号)是典型示例。
  • B错误:硬件异常信号(如除零、内存越界)忽略后可能导致进程状态异常,甚至破坏系统稳定性,不建议忽略,第一章1.9节明确此类信号不推荐忽略。
  • C正确 :第一章1.9节的信号捕捉示例(图1-10)中,signal函数的原型为void (*signal(int signo, void (*func)(int)))(int),注册失败返回SIG_ERR,书中实例均通过err_sys处理此类错误。
  • D正确 :标准输出默认是行缓冲,无换行符时缓冲不会自动冲洗,需手动调用fflush(stdout)确保提示及时显示(如图1-10的sig_int函数设计)。
  • E错误 :第一章1.7节明确errno的规则:"仅函数出错时有效,成功时不清除原值",因此不能通过errno是否为0判断函数是否出错,需先检查函数返回值(如返回-1)再查看errno

题目5 正确答案:ABD

  • A正确:第一章1.4节定义绝对路径与相对路径的核心区别是是否以"/"开头,".../doc/memo"从当前目录的父目录开始解析,属于相对路径。
  • B正确:第一章1.4节明确文件名的禁止字符为"/"(用于分隔路径组件)和"\0"(用于终止路径名),POSIX推荐的字符集可避免跨平台兼容性问题。
  • C错误 :第一章1.4节说明目录是内核保护的特殊文件,直接用read读取会破坏目录结构,必须通过opendir/readdir/closedir专用函数操作(如图1-3的ls简化实现)。
  • D正确:第一章1.4节提到,"./"和".../"是UNIX目录的默认项,根目录无父目录,因此"..."指向自身。
  • E错误:"/etc/passwd"以"/"开头,是绝对路径;"./test"不以"/"开头,是相对路径,混淆了两者的定义。
相关推荐
bendan502 小时前
统信UOS操作系统无“网络”选项下连接wifi
网络·统信uos·wifi链接
秋风不问归客2 小时前
linux 网络相关命令 及常用场景
linux·服务器·网络
wanzhong23332 小时前
开发日记2-创建http文件测试http接口
网络·网络协议·http
乾元2 小时前
Network-as-Code:把 HCIE / CCIE 实验脚本转为企业级 CI 工程化流程
运维·网络·人工智能·安全·web安全·ai·架构
应用市场3 小时前
嵌入式BIOS/Bootloader原理剖析——从启动流程到OTA自动升级实现
网络
天远Date Lab4 小时前
Python实现用户消费潜力评估:天远个人消费能力等级API对接全攻略
java·大数据·网络·python
岁岁种桃花儿12 小时前
Nginx 站点垂直扩容(单机性能升级)全攻略
网络·nginx·dns
Xの哲學12 小时前
Linux SMP 实现机制深度剖析
linux·服务器·网络·算法·边缘计算
一颗青果13 小时前
公网构建全流程与参与主体深度解析
网络