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