--- title: 面试-Linux应用编程基础 tags: [ 嵌入式Linux, 应用编程, 面试, 系统调用, 文件IO, 标准IO, 文件属性, 字符串, 系统信息, ] created: 2026-09-18 updated: 2026-09-18 --- # 面试-Linux应用编程基础 > 💡 **关联知识**:[[01-Linux应用编程基础/01-应用编程概念与开发环境]]、[[01-Linux应用编程基础/02-文件IO基础与深入探究]]、[[01-Linux应用编程基础/03-标准IO库]]、[[01-Linux应用编程基础/04-文件属性与目录]]、[[01-Linux应用编程基础/05-字符串处理与系统信息]]。 覆盖《I.MX6U嵌入式Linux C应用编程指南》第一章至第七章核心考点,共 6 大主题、30 道题。每题含「答案要点 / 详细解答(代码示例、对比表)/ 2 条追问」。 --- ## 一、应用编程概念(5 题) ### Q1.1 什么是系统调用?系统调用和库函数有什么区别? **答案要点**:系统调用是内核提供给应用层的编程接口,是应用层进入内核的入口;库函数是应用层封装的函数库,构建于系统调用之上。 **详细解答**:应用程序通过系统调用请求内核执行打开文件、读写、控制硬件等操作。两者的主要区别: | 对比项 | 系统调用 | 库函数 | | -------- | ------------------------ | ------------------------ | | 归属 | 内核的一部分 | 应用层 | | 运行空间 | 调用时从用户态陷入内核态 | 运行在用户空间 | | 缓冲 | 无缓存 | 通常有缓存 | | 可移植性 | 各系统不同 | 各系统接口几乎一致,更好 | 库函数有的不调用系统调用(如 `strlen`、`memcpy`),有的封装系统调用(`fopen` 内部调 `open`、`fread` 内部调 `read`)。从使用者角度看都是 C 函数,只需知道调用的是哪一类即可。 **追问** 1. `printf` 和 `write` 谁更快?为什么?(`printf` 有 stdio 缓冲,连续小量输出更快;`write` 每次直接陷入内核) 2. 为什么说库函数可移植性更好?(多数系统都实现 C 库,接口定义基本一致;系统调用的定义/参数/返回值各系统不同) ### Q1.2 应用编程与裸机编程、驱动编程有什么区别? **答案要点**:裸机编程无操作系统、软硬件代码混在一起;驱动编程基于内核驱动框架、负责底层硬件操作;应用编程基于操作系统、运行在用户态、只实现用户逻辑。 **详细解答**:以点亮 LED 为例: - 裸机:LED 硬件操作代码与用户逻辑在同一工程,代码直接跑在硬件上,无 OS; - 驱动:驱动属于内核的一部分,通过 `file_operations` 提供 open/write/release 等方法,运行在内核态; - 应用:只实现用户逻辑(`open`/`write`/`close` 设备文件),运行在用户态,与驱动分离、单独编译。 应用与驱动通过系统调用(设备文件)交互,代码隔离。 **追问** 1. 用户态和内核态如何切换?(通过系统调用/中断/异常由用户态陷入内核态) 2. 应用程序能直接访问硬件寄存器吗?为什么?(不能,需通过驱动/设备文件,否则破坏隔离与安全) ### Q1.3 什么是文件描述符 fd?0、1、2 分别是什么?一个进程最多能打开多少文件? **答案要点**:fd 是一个非负整数,内核用它索引所有已打开文件;0/1/2 分别是标准输入/输出/错误;默认上限 1024(0~1023)。 **详细解答**:`open` 成功返回 fd,所有 I/O 系统调用通过 fd 索引文件。fd 从 0 开始分配,总是取当前最小可用值;文件关闭后 fd 释放可被复用。0、1、2 默认被系统占用(stdin/stdout/stderr),所以程序首次 `open` 一般得到 3。 ```c #include /* 可直接用 0/1/2,也可用宏 */ write(STDOUT_FILENO, "hello\n", 6); ``` `ulimit -n` 可查看进程可打开最大文件数,默认 1024;超过限制内核会发送信号并结束进程。 **追问** 1. fd 是进程私有还是全系统共享?(进程私有;不同进程同一 fd 编号互不相关) 2. 关闭 fd 后,另一个 `open` 会分配哪个编号?(当前最小可用编号,通常就是刚释放的那个) ### Q1.4 main 函数的 `argc` 和 `argv` 分别是什么? **答案要点**:`argc` 是参数个数(含程序自身路径),`argv` 是字符串指针数组,`argv[0]` 是程序路径、`argv[1]` 起是真实参数。 **详细解答**:原型 `int main(int argc, char **argv)`。执行 `./app 112233` 时 `argc=2`,`argv[0]="./app"`,`argv[1]="112233"`;参数以空格分隔,带空格的参数用引号。应用编程中还可用 `getopt` 解析选项。 ```c int main(int argc, char *argv[]) { for (int i = 0; i < argc; i++) printf("argv[%d]=%s\n", i, argv[i]); return 0; } ``` **追问** 1. `argv` 最后一个元素是什么?(`argv[argc]` 为 `NULL`) 2. 如何让程序接收选项参数?(用 `getopt`/`getopt_long`,参数放入 `optarg`) ### Q1.5 glibc 是什么?如何确定当前系统的 glibc 版本? **答案要点**:glibc 是 GNU C 库、Linux 下的标准 C 函数库,通常以动态库 `libc.so.6` 形式提供。 **详细解答**:glibc 构建在系统调用之上,向上提供标准 C 库函数。`libc.so.6` 一般是软链接,指向真正的库文件(如 `libc-2.23.so`,2.23 即版本号)。可直接运行共享库查询: ```bash ls -l /lib/x86_64-linux-gnu/libc.so.6 # 看软链接指向 /lib/x86_64-linux-gnu/libc.so.6 # 运行库本身会打印版本 ``` **追问** 1. 为什么应用编程可以基本忽略系统调用与库函数的差异?(都表现为 C 函数,使用方式一致) 2. 交叉编译时 glibc 版本为何重要?(目标板 glibc 版本需不低于编译链所用版本,否则运行时报符号缺失) --- ## 二、文件 I/O(5 题) ### Q2.1 `open` 的 flags 有哪些?什么时候需要第三个参数 `mode`? **答案要点**:访问模式 `O_RDONLY`/`O_WRONLY`/`O_RDWR` 必须且只能选一个;`O_CREAT`/`O_EXCL`/`O_TRUNC`/`O_APPEND`/`O_NOFOLLOW`/`O_DIRECTORY` 等可组合;仅当含 `O_CREAT`(或 `O_TMPFILE`)时 `mode` 才有效。 **详细解答**: | 标志 | 用途 | | ---------------------------------- | --------------------------------------- | | `O_RDONLY` / `O_WRONLY` / `O_RDWR` | 只读 / 只写 / 读写,三选一 | | `O_CREAT` | 文件不存在则创建,需配合 `mode` | | `O_EXCL` | 与 `O_CREAT` 合用专门创建;已存在则报错 | | `O_TRUNC` | 打开时把文件长度截断为 0 | | `O_APPEND` | 每次写前把偏移量移到文件末尾(原子) | | `O_NOFOLLOW` | 路径是符号链接时不解析,直接报错 | | `O_DIRECTORY` | 路径不是目录则失败 | ```c int fd = open("/home/dt/hello", O_RDWR | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH); ``` **追问** 1. `O_EXCL` 解决什么问题?(把「判断是否存在」与「创建」合成原子操作,避免两个进程都创建同一文件) 2. `O_TRUNC` 和 `O_APPEND` 能一起用吗?效果?(可以,但语义矛盾,实际以追加为主,需谨慎) ### Q2.2 `read`/`write` 的返回值含义?返回的字节数小于请求数算错吗? **答案要点**:返回实际读/写的字节数;小于 `count` 通常不是错误,0 表示读到文件末尾或未写入;出错返回 -1。 **详细解答**:`ssize_t read(int fd, void *buf, size_t count)`、`ssize_t write(int fd, const void *buf, size_t count)`。对普通文件,读写从当前偏移量开始,完成后偏移量自动后移相应字节数。`read` 在到达文件末尾时返回 0;若剩余数据不足 `count`,返回剩余数量。`write` 若磁盘满等情况可能返回小于 `count`,应循环写完。 ```c ssize_t n; while ((n = read(fd, buf, sizeof(buf))) > 0) { ssize_t off = 0; while (off < n) { ssize_t w = write(fd2, buf + off, n - off); if (w < 0) { perror("write"); break; } off += w; } } ``` **追问** 1. `read` 返回 0 一定表示文件结束吗?(对普通文件是;对管道/终端等需结合场景) 2. `write` 返回 0 代表什么?(未写入任何字节,通常表示异常或特殊设备行为) ### Q2.3 `lseek` 的三个 `whence` 是什么?如何用它求文件大小? **答案要点**:`SEEK_SET` 相对文件头、`SEEK_CUR` 相对当前位置、`SEEK_END` 相对文件末尾;`lseek(fd, 0, SEEK_END)` 得到文件大小。 **详细解答**:`off_t lseek(int fd, off_t offset, int whence)`,成功返回从文件头算起的新偏移量,失败 -1。偏移量可正可负。求大小: ```c off_t size = lseek(fd, 0, SEEK_END); /* 返回文件字节数 */ lseek(fd, 0, SEEK_SET); /* 重新定位到文件头 */ ``` **追问** 1. 空洞文件是什么?(`lseek` 跳过一段后写入,中间未写区域形成空洞,逻辑大小包含空洞,但不占实际块) 2. 设备文件能 `lseek` 吗?(可以,但部分设备不支持,返回错误) ### Q2.4 一个进程内多次 `open` 同一个文件会发生什么? **答案要点**:得到多个不同 fd;内存中只有一份动态文件(共享);各 fd 的读写偏移量相互独立(分别写),需用 `O_APPEND` 才能接续写。 **详细解答**:多次 `open` 得到多个 fd,对应多个文件表,但文件表都指向同一个 inode,因此内存中只有一份文件数据。由于偏移量记录在文件表中,各 fd 的偏移量独立,`write` 时是**分别写**(互相覆盖);加 `O_APPEND` 后每次写前自动定位到末尾,变成**接续写**。多个进程打开同一文件同理。 ```c fd1 = open("f", O_RDWR | O_APPEND); fd2 = open("f", O_RDWR | O_APPEND); /* fd1/fd2 写操作接续 */ ``` **追问** 1. `dup` 复制出的 fd 与原 fd 的偏移量是否独立?(不独立,指向同一文件表,共享偏移量) 2. 引用计数为 0 时会发生什么?(内核关闭动态文件,回收相关资源) ### Q2.5 什么是竞争冒险和原子操作?Linux 提供了哪些原子化手段? **答案要点**:竞争冒险是多个进程/线程无序操作共享资源导致结果不可预期;原子操作是不可分割、要么全做要么全不做的操作。 **详细解答**:两进程 A、B 都对同一文件追加写且未用 `O_APPEND`:A `lseek` 到末尾后时间片耗尽,B 也 `lseek` 到同一末尾并写入,A 恢复后再从旧位置写,覆盖了 B 的数据。原子化手段: | 手段 | 原子化的操作 | | ------------------- | -------------------------------------- | | `O_APPEND` | 「定位到文件末尾 + 写入」 | | `pread` / `pwrite` | 「定位 + 读/写」,且不改变文件表偏移量 | | `O_CREAT \| O_EXCL` | 「判断文件是否存在 + 创建」 | ```c /* pread 不改变当前偏移量 */ ssize_t n = pread(fd, buf, sizeof(buf), 1024); ``` **追问** 1. `pread` 会改变文件偏移量吗?(不会,这是它与 `lseek`+`read` 的关键区别) 2. 应用层加锁能替代原子操作吗?(可以配合文件锁,但原子系统调用更简单可靠) --- ## 三、标准 I/O(5 题) ### Q3.1 `FILE` 指针和文件描述符 fd 是什么关系?如何互转? **答案要点**:`FILE` 是标准 I/O 使用的结构体指针,内部封装了 fd 及 stdio 缓冲区;用 `fileno` 把 `FILE *` 转为 fd,用 `fdopen` 把 fd 转为 `FILE *`。 **详细解答**:标准 I/O 在文件 I/O 之上封装,`fopen` 内部调 `open`、`fread` 内部调 `read`。`FILE` 结构包含 fd、缓冲区、偏移等。 ```c #include int fd = fileno(fp); /* FILE* -> fd */ FILE *fp2 = fdopen(fd, "r"); /* fd -> FILE* */ ``` 混用时要注意缓冲:`printf("print"); write(STDOUT_FILENO,"write\n",6);` 会先输出 `write`,因为 `print` 还在行缓冲中,而 `write` 直接进内核缓冲区。 **追问** 1. 混用标准 I/O 和文件 I/O 要注意什么?(缓冲不同步,可能乱序;必要时 `fflush`) 2. `fdopen` 的 `mode` 与 fd 不一致会怎样?(失败) ### Q3.2 标准输入、输出、错误分别是什么?三种缓冲模式是什么? **答案要点**:stdin=0、stdout=1、stderr=2;三种模式为无缓冲 `_IONBF`、行缓冲 `_IOLBF`、全缓冲 `_IOFBF`。 **详细解答**: | 模式 | 行为 | 默认用于 | | --------------- | --------------------------- | -------------------- | | `_IONBF` 无缓冲 | 每次调用立即 `read`/`write` | `stderr` | | `_IOLBF` 行缓冲 | 遇换行符才刷出 | 终端(stdin/stdout) | | `_IOFBF` 全缓冲 | 缓冲区填满才刷出 | 磁盘普通文件 | ```c setvbuf(stdout, NULL, _IONBF, 0); /* 改为无缓冲 */ setbuf(fp, NULL); /* 等价无缓冲 */ ``` **追问** 1. 为什么 `stderr` 默认无缓冲?(保证错误信息能立即输出) 2. 为什么 `printf` 不带换行时不立即显示?(stdout 行缓冲,数据仍在 stdio 缓冲区) ### Q3.3 如何强制刷新 stdio 缓冲区?哪些情况会自动刷新? **答案要点**:用 `fflush(stream)` 强制刷新(`NULL` 表示全部);文件关闭(`fclose`)时、程序正常退出时也会自动刷新。 **详细解答**: ```c printf("Hello World!"); fflush(stdout); /* 立即刷出 */ ``` 自动刷新场景:`fclose` 关闭文件时;使用 `exit()`、`return` 或正常结束程序时。注意:`_exit` / `_Exit` 终止程序**不会**刷新 stdio 缓冲区。 **追问** 1. `_exit` 和 `exit` 的区别?(`exit` 会刷新 stdio、执行 atexit;`_exit` 直接陷入内核终止,不刷新) 2. `fflush(stdin)` 有用吗?(不标准,通常无效或未定义,不要依赖) ### Q3.4 标准 I/O 与文件 I/O 有哪些常用函数对应关系? **答案要点**:标准 I/O 是系统调用的封装,且自带用户空间缓冲。 **详细解答**: | 文件 I/O(系统调用) | 标准 I/O(库函数) | | -------------------- | ---------------------------------------- | | `open` | `fopen` / `freopen` | | `close` | `fclose` | | `read` | `fread` | | `write` | `fwrite` | | `lseek` | `fseek` / `ftell` / `rewind` | | — | `fgets` / `fputs` / `fprintf` / `fscanf` | | fd | `FILE *` | 标准 I/O 的 `fseek` 只能用 `SEEK_SET`/`SEEK_CUR`/`SEEK_END`,且受 stdio 缓冲影响。 **追问** 1. 何时选文件 I/O、何时选标准 I/O?(需要精细控制/设备操作用文件 I/O;文本处理、格式化输入输出用标准 I/O) 2. `fseek` 后为什么有时要 `fflush`?(读写模式切换需刷新缓冲区,避免数据错乱) ### Q3.5 为什么标准 I/O 通常比文件 I/O 效率高?直接 I/O 又是什么? **答案要点**:标准 I/O 有用户空间 stdio 缓冲,减少系统调用次数;直接 I/O 绕过内核缓冲,直接访问磁盘。 **详细解答**:文件 I/O 每次 `read`/`write` 都会陷入内核;标准 I/O 先把数据攒在 stdio 缓冲区,攒够再一次性 `write`,显著减少陷入次数。缓冲层次:应用 → stdio 缓冲区(用户空间)→ 内核缓冲区 → 磁盘。`fflush` 刷 stdio 缓冲,`fsync`/`fdatasync`/`sync` 刷内核缓冲,`open` 加 `O_DIRECT` 则绕过内核缓冲(直接 I/O),但有对齐限制且性能通常更低。 **追问** 1. `fsync` 和 `fdatasync` 的区别?(`fsync` 刷数据 + 元数据,`fdatasync` 只刷数据) 2. 拷贝大文件时如何提升可靠性?(拷贝完 `fsync` 目标文件,或 `O_SYNC`/`O_DSYNC`) --- ## 四、文件属性与目录(5 题) ### Q4.1 Linux 有哪 7 种文件类型?`ls -l` 如何表示? **答案要点**:普通文件 `-`、目录 `d`、字符设备 `c`、块设备 `b`、符号链接 `l`、套接字 `s`、管道 `p`。 **详细解答**:`ls -l` 第一列首字符标识类型。普通文件分文本文件与二进制文件;设备文件对应硬件但**不占磁盘**(虚拟文件系统,关机消失),常在 `/dev`;符号链接类似 Windows 快捷方式;管道和套接字用于进程间通信,后者可跨主机。Linux **不通过后缀识别类型**。 | 类型 | 字符 | 典型 | | -------- | ---- | ---------------------- | | 普通文件 | `-` | `.c`、`.o`、可执行文件 | | 目录 | `d` | 文件夹 | | 字符设备 | `c` | 串口、按键 | | 块设备 | `b` | 硬盘、loop | | 符号链接 | `l` | 快捷方式 | | 套接字 | `s` | 网络通信 | | 管道 | `p` | 进程间通信 | **追问** 1. 如何用编程方式判断类型?(`stat` 取 `st_mode`,用 `S_ISREG`/`S_ISDIR` 等宏) 2. 设备文件为什么关机就消失?(由 devfs 虚拟产生,由内存维护,不写磁盘) ### Q4.2 `stat`、`fstat`、`lstat` 有什么区别? **答案要点**:`stat` 从路径且解引用链接;`fstat` 从 fd;`lstat` 从路径且不解引用链接。 **详细解答**:三者都填充 `struct stat`。`stat`/`lstat` 不需先打开文件,`fstat` 需先 `open`。对符号链接,`stat`/`fstat` 得到所指文件属性,`lstat` 得到链接本身属性。 ```c struct stat st; stat("link", &st); /* 链接所指文件 */ lstat("link", &st); /* 链接本身, S_ISLNK(st.st_mode) 为真 */ ``` **追问** 1. 判断路径是否为符号链接该用哪个?(`lstat` + `S_ISLNK`) 2. `struct stat` 的 `st_size` 对符号链接意味着什么?(链接数据块中路径字符串的长度) ### Q4.3 文件权限如何检查?目录权限的含义有何不同?`umask` 和 `access` 各有什么作用? **答案要点**:以进程有效用户/组、附属组对应文件所有者/组/其它角色检查;目录 r=可列出、w=可增删文件、x=可访问其中文件;`umask` 屏蔽新建权限,`access` 检查权限(用实际 ID)。 **详细解答**:权限检查规则:有效用户 ID 等于文件所有者 ID 则以所有者身份检查;否则有效组或任一附属组等于文件组 ID 则以同组身份检查;否则以其它用户身份检查;有效用户 ID 为 0 则拥有最高权限。对文件读写执行还需拥有**所在目录的执行权限**;在目录下创建/删除文件需目录的写 + 执行权限。 `umask`:新建权限 = `mode & ~umask`(如 `0777 & ~0002 = 0775`)。`access` 用**实际**用户/组判断,在 SUID 场景下结果可能不等于真正 `open`,且存在 TOCTOU 风险,推荐直接 `open` 后看 `errno`。 ```c if (access("f", R_OK | W_OK) == 0) puts("rw ok"); ``` **追问** 1. set-user-ID 位的作用?(进程操作该文件时有效用户 ID 变为文件所有者 ID) 2. 为什么只读目录无法访问文件?(缺执行权限,无法通过目录解析文件) ### Q4.4 硬链接和软链接的区别与各自限制? **答案要点**:硬链接共享同一 inode、计入 `st_nlink`;软链接独立 inode、数据块存目标路径、不计入链接数。 **详细解答**: | 对比项 | 硬链接 | 软链接 | | ---------- | ------------ | ---------- | | inode | 相同 | 不同 | | 数据块 | 共享 | 存目标路径 | | 源文件删除 | 仍可访问 | 悬空 | | 链接数 | 计入 | 不计入 | | 目录 | 不能(一般) | 可以 | | 跨文件系统 | 不能 | 可以 | | 目标不存在 | 不允许 | 允许 | ```c link("src", "hard"); /* 硬链接 */ symlink("src", "soft"); /* 软链接 */ char buf[100]; readlink("soft", buf, sizeof(buf)); /* 读软链接路径 */ ``` 硬链接不能跨文件系统(inode 只在单个文件系统内唯一)、一般不能指向目录(会形成目录环)。读取软链接存储的路径必须用 `readlink`,不能 `open`+`read`。 **追问** 1. 删除源文件后软链接还能读吗?(不能,成悬空链接,`readlink` 仍能读出路径字符串) 2. `unlink` 一个软链接删的是什么?(软链接本身,不影响源文件) ### Q4.5 三个时间戳分别是什么?目录如何遍历?如何删除与重命名文件? **答案要点**:`atime` 访问、`mtime` 内容修改、`ctime` 状态改变;目录用 `opendir`/`readdir`/`closedir`;删除用 `unlink`/`remove`/`rmdir`,重命名用 `rename`。 **详细解答**:`atime` 读文件时更新,`mtime` 写文件时更新,`ctime` 在 inode 变更时更新(`chmod`/`chown`/`rename`)。可显式修改 `atime`/`mtime`(`utime` 秒、`utimes` 微秒、`futimens`/`utimensat` 纳秒),`ctime` 不能显式修改。 ```c DIR *d = opendir("."); struct dirent *e; errno = 0; while ((e = readdir(d)) != NULL) printf("%s %ld\n", e->d_name, (long)e->d_ino); if (errno) perror("readdir"); closedir(d); ``` `unlink` 删文件并把链接计数减 1;`remove` 自动区分文件/空目录;`rmdir` 只能删空目录;`rename` 只改目录条目,inode 不变,要求同一文件系统,不能重命名 `.`/`..`。软链接的 `unlink`/`remove` 都是删链接本身。 **追问** 1. `readdir` 返回 `NULL` 如何区分末尾和出错?(先置 `errno=0`,返回 `NULL` 后看 `errno` 是否为 0) 2. `rename` 会移动文件数据吗?(不会,只操作目录条目,inode 与数据块不变) --- ## 五、字符串处理(5 题) ### Q5.1 `sizeof` 和 `strlen` 有什么区别? **答案要点**:`sizeof` 是编译期操作符,算内存大小;`strlen` 是运行时库函数,算到 `\0` 的长度。 **详细解答**: | 对比项 | `sizeof` | `strlen` | | ------ | ------------ | -------------------- | | 本质 | 操作符关键字 | C 库函数 | | 对象 | 类型/变量 | 以 `\0` 结尾的字符串 | | 时机 | 编译时 | 运行时 | | 结果 | 占用内存大小 | 字符串实际长度 | ```c char str[50] = "abc"; char *p = str; printf("%zu %zu %zu %zu\n", sizeof(str), strlen(str), sizeof(p), strlen(p)); /* 输出: 50 3 8 3 (64 位系统) */ ``` **追问** 1. 函数参数 `char s[]` 的 `sizeof(s)` 是多少?(指针大小,数组退化为指针) 2. `strlen` 对不含 `\0` 的数组会怎样?(越界读取,未定义行为) ### Q5.2 `strcpy` / `strncpy` / `memcpy` 有什么区别? **答案要点**:`strcpy` 复制到 `\0`;`strncpy` 限长且可能不补 `\0`;`memcpy` 按字节复制、不关心 `\0`。 **详细解答**: | 函数 | 边界 | 是否复制 `\0` | 场景 | | --------- | ----------- | ----------------- | ---------------- | | `strcpy` | 到 `\0` | 是 | 已知源长度安全时 | | `strncpy` | 最多 n 字节 | `n <= len` 时不带 | 固定长度字段 | | `memcpy` | n 字节 | 不关心 | 结构体/二进制 | ```c char d[16]; strncpy(d, "hello world", 5); d[5] = '\0'; /* 手工补 '\0' */ memcpy(d, "abc", 3); /* 不关 '\0' */ ``` `memcpy` 要求源与目标不重叠,重叠用 `memmove`。 **追问** 1. `strncpy(d, s, n)` 在 `n > strlen(s)` 时如何?(不足部分用 `\0` 填充) 2. 拼接函数 `strcat` 有什么风险?(可能溢出,用 `strncat` 限定长度) ### Q5.3 字符串拼接、比较、查找各用什么函数? **答案要点**:拼接 `strcat`/`strncat`;比较 `strcmp`/`strncmp`;查找字符 `strchr`/`strrchr`、子串 `strstr`。 **详细解答**: | 函数 | 作用 | 返回值 | | ------------------ | ---------------- | ------------- | | `strcat`/`strncat` | 追加到末尾 | `dest` | | `strcmp`/`strncmp` | 按 ASCII 比较 | `<0`/`0`/`>0` | | `strchr` | 首次出现字符 | 指针 / `NULL` | | `strrchr` | 最后一次出现字符 | 指针 / `NULL` | | `strstr` | 首次出现子串 | 指针 / `NULL` | ```c char s[64] = "Linux "; strncat(s, "app", 3); /* "Linux app" */ int r = strcmp("ABC", "abc"); /* < 0 (大写 ASCII 更小) */ char *p = strstr("I love my home", "home"); /* 指向 "home" */ ``` **追问** 1. `strcat` 是否检查 `dest` 容量?(不检查,必须保证足够空间) 2. `strchr(s, '\0')` 返回什么?(指向字符串结束符的指针) ### Q5.4 字符串与数字如何互转? **答案要点**:字符串转整型用 `atoi`/`atol`/`atoll`/`strtol`;转浮点用 `atof`/`strtod`;数字转字符串用 `sprintf`/`snprintf`。 **详细解答**:`atoi` 系列只支持十进制;`strtol`/`strtoll`/`strtoul`/`strtoull` 支持 `base` 2~36,`base=0` 时自动识别 `0x`(16 进制)/`0`(8 进制)前缀。`endptr` 可获取第一个无效字符地址。数字转字符串推荐 `snprintf` 防溢出。 ```c int i = atoi("500"); /* 十进制 */ long l = strtol("0x500", NULL, 16); /* 16 进制 = 1280 */ double d = atof("100.0123"); char s[20]; snprintf(s, sizeof(s), "%d", i); ``` **追问** 1. `atoi` 遇到 `"abc"` 返回什么?(0,且无法区分「本来就是 0」和「转换失败」) 2. 如何判断转换是否成功?(用 `strtol` 检查 `endptr` 与 `errno`) ### Q5.5 POSIX 正则表达式的使用流程是什么? **答案要点**:`regcomp` 编译 → `regexec` 匹配 → `regfree` 释放,出错时用 `regerror` 取描述。 **详细解答**: | 函数 | 作用 | 返回值 | | ---------- | ------------ | ---------------------------- | | `regcomp` | 编译正则 | 0 成功,非 0 错误码 | | `regexec` | 执行匹配 | 0 匹配,`REG_NOMATCH` 不匹配 | | `regerror` | 错误码转描述 | 长度 | | `regfree` | 释放资源 | 无 | `REG_EXTENDED` 启用扩展语法(`+`、`?`、`{}`、`|`、`()`)。`regmatch_t` 的 `rm_so`/`rm_eo` 是匹配子串的起止偏移(左闭右开)。 ```c regex_t reg; regmatch_t pm; regcomp(®, "[0-9]+", REG_EXTENDED); if (regexec(®, "abc123", 1, &pm, 0) == 0) printf("%.*s\n", (int)(pm.rm_eo - pm.rm_so), "abc123" + pm.rm_so); /* 123 */ regfree(®); ``` **追问** 1. 如何循环提取所有匹配项?(每次把指针前移 `rm_eo` 再 `regexec`,空匹配时前移 1) 2. `REG_EXTENDED` 与默认(BRE)语法区别?(BRE 中 `+`/`?`/`|`/`()` 需转义才生效) --- ## 六、系统信息与系统资源(5 题) ### Q6.1 如何获取系统信息?`uname`、`sysinfo`、`gethostname`、`sysconf` 各做什么? **答案要点**:`uname` 取操作系统/内核/架构;`sysinfo` 取内存/进程/运行时长;`gethostname` 取主机名;`sysconf` 取运行时配置。 **详细解答**: | 函数 | 获取内容 | 头文件 | | ------------- | ------------------------------------------------ | ----------------- | | `uname` | sysname/nodename/release/version/machine | `` | | `sysinfo` | uptime、totalram、freeram、procs 等 | `` | | `gethostname` | 主机名 | `` | | `sysconf` | `_SC_PAGESIZE`、`_SC_CLK_TCK`、`_SC_OPEN_MAX` 等 | `` | ```c struct sysinfo si; if (sysinfo(&si) == 0) printf("totalram=%lu bytes\n", si.totalram * si.mem_unit); ``` 内存字节数 = 字段值 × `mem_unit`。 **追问** 1. 如何取系统节拍率和页大小?(`sysconf(_SC_CLK_TCK)`、`sysconf(_SC_PAGESIZE)`) 2. `/proc/meminfo` 与 `sysinfo` 有何关系?(都是内核内存信息的展现方式,`/proc` 以虚拟文件呈现) ### Q6.2 解释 GMT、UTC、时区、RTC 与 jiffies。 **答案要点**:GMT 是格林威治标准时间;UTC 是世界协调时间(比 GMT 精准);时区配置在 `/usr/share/zoneinfo`,本地时间由 `/etc/localtime` 定义;RTC 是断电仍计时的硬件时钟;jiffies 记录系统节拍数。 **详细解答**:Linux 开机读取一次 RTC 初始化系统时钟(即初始化 jiffies),之后内核自行维护系统时钟;关机时把系统时钟写回 RTC。jiffies 是内核全局变量,节拍率(如 100Hz)越低精度越低、中断负担越小,默认 100Hz。北京时间(东八区)比 UTC 早 8 小时。 **追问** 1. 如何查看/修改系统时区?(`date` 查看,`/etc/localtime` 软链接到 zoneinfo 下目标文件) 2. RTC 和系统时钟谁先存在?(RTC 由电池供电长期存在;系统时钟开机时由 RTC 初始化) ### Q6.3 获取时间与时间转换函数有哪些? **答案要点**:获取时间 `time`(秒)、`gettimeofday`(微秒);转换 `ctime`/`localtime`/`gmtime`/`mktime`/`asctime`/`strftime`。 **详细解答**: | 函数 | 方向 | 说明 | | ------------------------- | ----------- | -------------------------- | | `time` | → time_t | 自 1970-01-01 UTC 起的秒数 | | `gettimeofday` | → timeval | 秒 + 微秒 | | `localtime`/`localtime_r` | time_t → tm | 本地时间 | | `gmtime`/`gmtime_r` | time_t → tm | UTC 时间 | | `mktime` | tm → time_t | 与 localtime 相反 | | `ctime`/`asctime` | → 字符串 | 固定格式 | | `strftime` | tm → 字符串 | 自定义格式 | ```c time_t t = time(NULL); struct tm tm; char s[100]; localtime_r(&t, &tm); strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", &tm); ``` 注意 `tm_year` 加 1900、`tm_mon` 加 1;推荐使用可重入的 `_r` 版本。 **追问** 1. `%Y` 和 `%y`、`%m` 和 `%M` 分别是什么?(4 位年/2 位年;月/分) 2. 夏令时字段是什么?(`tm_isdst`) ### Q6.4 进程时间是什么?`times` 和 `clock` 有什么区别? **答案要点**:进程时间 = 用户 CPU 时间 + 系统 CPU 时间;`times` 可区分用户/系统时间,`clock` 只给总时间。 **详细解答**:休眠不消耗 CPU、不计入进程时间,要与「时间总和」区分。`times(&tms)` 返回节拍数,`节拍数 / _SC_CLK_TCK` 得秒;`struct tms` 提供 `tms_utime`、`tms_stime`、子进程时间。`clock()` 返回总 CPU 时间,除以 `CLOCKS_PER_SEC` 得秒。 ```c struct tms tb; long tck = sysconf(_SC_CLK_TCK); times(&tb); printf("user=%.3fs\n", tb.tms_utime / (double)tck); ``` **追问** 1. 为什么进程时间可能远小于程序运行时间?(进程休眠/阻塞期间不占 CPU) 2. `clock()` 能区分用户态和内核态吗?(不能) ### Q6.5 随机数、休眠、堆内存与 `/proc` 各要注意什么? **答案要点**:随机数用 `rand`/`srand`(伪随机,种子常用 `time(NULL)`);休眠 `sleep`/`usleep`/`nanosleep`;堆内存 `malloc`/`free`(`calloc` 置 0);`/proc` 是虚拟文件系统,用 `open`+`read` 或 `cat` 读取。 **详细解答**: ```c srand(time(NULL)); /* 随机种子, 否则默认 1 */ int n = rand() % 100; /* [0,99] */ sleep(1); /* 秒级, 主动交出 CPU */ struct timespec ts = {3, 0}; nanosleep(&ts, NULL); /* 纳秒级, 系统调用 */ char *p = malloc(1024); /* 不初始化 */ memset(p, 0, 1024); free(p); /* 及时释放 */ ``` `/proc` 下常见文件:`version`(内核版本)、`cpuinfo`、`meminfo`、`stat`、`uptime`、`cmdline` 等。 **追问** 1. 进程退出内核会回收内存,为什么还要 `free`?(避免阶段性命周期内的内存泄漏,提升可维护性) 2. `sleep` 和裸机 `for` 循环延时的本质区别?(`sleep` 主动让出 CPU,后者一直占用 CPU 空转) --- **内容来源**:《I.MX6U嵌入式Linux C应用编程指南》第一章~第七章