面试-Linux应用编程基础.md 32 KB


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 什么是系统调用?系统调用和库函数有什么区别?

答案要点:系统调用是内核提供给应用层的编程接口,是应用层进入内核的入口;库函数是应用层封装的函数库,构建于系统调用之上。

详细解答:应用程序通过系统调用请求内核执行打开文件、读写、控制硬件等操作。两者的主要区别:

对比项 系统调用 库函数
归属 内核的一部分 应用层
运行空间 调用时从用户态陷入内核态 运行在用户空间
缓冲 无缓存 通常有缓存
可移植性 各系统不同 各系统接口几乎一致,更好

库函数有的不调用系统调用(如 strlenmemcpy),有的封装系统调用(fopen 内部调 openfread 内部调 read)。从使用者角度看都是 C 函数,只需知道调用的是哪一类即可。

追问

  1. printfwrite 谁更快?为什么?(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。

#include <unistd.h>
/* 可直接用 0/1/2,也可用宏 */
write(STDOUT_FILENO, "hello\n", 6);

ulimit -n 可查看进程可打开最大文件数,默认 1024;超过限制内核会发送信号并结束进程。

追问

  1. fd 是进程私有还是全系统共享?(进程私有;不同进程同一 fd 编号互不相关)
  2. 关闭 fd 后,另一个 open 会分配哪个编号?(当前最小可用编号,通常就是刚释放的那个)

Q1.4 main 函数的 argcargv 分别是什么?

答案要点argc 是参数个数(含程序自身路径),argv 是字符串指针数组,argv[0] 是程序路径、argv[1] 起是真实参数。

详细解答:原型 int main(int argc, char **argv)。执行 ./app 112233argc=2argv[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;
}

追问

  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 即版本号)。可直接运行共享库查询:

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 路径不是目录则失败
int fd = open("/home/dt/hello", O_RDWR | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);

追问

  1. O_EXCL 解决什么问题?(把「判断是否存在」与「创建」合成原子操作,避免两个进程都创建同一文件)
  2. O_TRUNCO_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,应循环写完。

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。偏移量可正可负。求大小:

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 后每次写前自动定位到末尾,变成接续写。多个进程打开同一文件同理。

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 「判断文件是否存在 + 创建」
/* 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 缓冲区;用 filenoFILE * 转为 fd,用 fdopen 把 fd 转为 FILE *

详细解答:标准 I/O 在文件 I/O 之上封装,fopen 内部调 openfread 内部调 readFILE 结构包含 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 直接进内核缓冲区。

追问

  1. 混用标准 I/O 和文件 I/O 要注意什么?(缓冲不同步,可能乱序;必要时 fflush
  2. fdopenmode 与 fd 不一致会怎样?(失败)

Q3.2 标准输入、输出、错误分别是什么?三种缓冲模式是什么?

答案要点: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);                   /* 等价无缓冲 */

追问

  1. 为什么 stderr 默认无缓冲?(保证错误信息能立即输出)
  2. 为什么 printf 不带换行时不立即显示?(stdout 行缓冲,数据仍在 stdio 缓冲区)

Q3.3 如何强制刷新 stdio 缓冲区?哪些情况会自动刷新?

答案要点:用 fflush(stream) 强制刷新(NULL 表示全部);文件关闭(fclose)时、程序正常退出时也会自动刷新。

详细解答

printf("Hello World!");
fflush(stdout);          /* 立即刷出 */

自动刷新场景:fclose 关闭文件时;使用 exit()return 或正常结束程序时。注意:_exit / _Exit 终止程序不会刷新 stdio 缓冲区。

追问

  1. _exitexit 的区别?(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 刷内核缓冲,openO_DIRECT 则绕过内核缓冲(直接 I/O),但有对齐限制且性能通常更低。

追问

  1. fsyncfdatasync 的区别?(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. 如何用编程方式判断类型?(statst_mode,用 S_ISREG/S_ISDIR 等宏)
  2. 设备文件为什么关机就消失?(由 devfs 虚拟产生,由内存维护,不写磁盘)

Q4.2 statfstatlstat 有什么区别?

答案要点stat 从路径且解引用链接;fstat 从 fd;lstat 从路径且不解引用链接。

详细解答:三者都填充 struct statstat/lstat 不需先打开文件,fstat 需先 open。对符号链接,stat/fstat 得到所指文件属性,lstat 得到链接本身属性。

struct stat st;
stat("link", &st);    /* 链接所指文件 */
lstat("link", &st);   /* 链接本身, S_ISLNK(st.st_mode) 为真 */

追问

  1. 判断路径是否为符号链接该用哪个?(lstat + S_ISLNK
  2. struct statst_size 对符号链接意味着什么?(链接数据块中路径字符串的长度)

Q4.3 文件权限如何检查?目录权限的含义有何不同?umaskaccess 各有什么作用?

答案要点:以进程有效用户/组、附属组对应文件所有者/组/其它角色检查;目录 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");

追问

  1. set-user-ID 位的作用?(进程操作该文件时有效用户 ID 变为文件所有者 ID)
  2. 为什么只读目录无法访问文件?(缺执行权限,无法通过目录解析文件)

Q4.4 硬链接和软链接的区别与各自限制?

答案要点:硬链接共享同一 inode、计入 st_nlink;软链接独立 inode、数据块存目标路径、不计入链接数。

详细解答

对比项 硬链接 软链接
inode 相同 不同
数据块 共享 存目标路径
源文件删除 仍可访问 悬空
链接数 计入 不计入
目录 不能(一般) 可以
跨文件系统 不能 可以
目标不存在 不允许 允许
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/mtimeutime 秒、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 都是删链接本身。

追问

  1. readdir 返回 NULL 如何区分末尾和出错?(先置 errno=0,返回 NULL 后看 errno 是否为 0)
  2. rename 会移动文件数据吗?(不会,只操作目录条目,inode 与数据块不变)

五、字符串处理(5 题)

Q5.1 sizeofstrlen 有什么区别?

答案要点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 位系统) */

追问

  1. 函数参数 char s[]sizeof(s) 是多少?(指针大小,数组退化为指针)
  2. strlen 对不含 \0 的数组会怎样?(越界读取,未定义行为)

Q5.2 strcpy / strncpy / memcpy 有什么区别?

答案要点strcpy 复制到 \0strncpy 限长且可能不补 \0memcpy 按字节复制、不关心 \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

追问

  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
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 防溢出。

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 检查 endptrerrno

Q5.5 POSIX 正则表达式的使用流程是什么?

答案要点regcomp 编译 → regexec 匹配 → regfree 释放,出错时用 regerror 取描述。

详细解答

函数 作用 返回值
regcomp 编译正则 0 成功,非 0 错误码
regexec 执行匹配 0 匹配,REG_NOMATCH 不匹配
regerror 错误码转描述 长度
regfree 释放资源

REG_EXTENDED 启用扩展语法(+?{}|())。regmatch_trm_so/rm_eo 是匹配子串的起止偏移(左闭右开)。

regex_t reg; regmatch_t pm;
regcomp(&reg, "[0-9]+", REG_EXTENDED);
if (regexec(&reg, "abc123", 1, &pm, 0) == 0)
    printf("%.*s\n", (int)(pm.rm_eo - pm.rm_so), "abc123" + pm.rm_so); /* 123 */
regfree(&reg);

追问

  1. 如何循环提取所有匹配项?(每次把指针前移 rm_eoregexec,空匹配时前移 1)
  2. REG_EXTENDED 与默认(BRE)语法区别?(BRE 中 +/?/|/() 需转义才生效)

六、系统信息与系统资源(5 题)

Q6.1 如何获取系统信息?unamesysinfogethostnamesysconf 各做什么?

答案要点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

追问

  1. 如何取系统节拍率和页大小?(sysconf(_SC_CLK_TCK)sysconf(_SC_PAGESIZE)
  2. /proc/meminfosysinfo 有何关系?(都是内核内存信息的展现方式,/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 → 字符串 自定义格式
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 进程时间是什么?timesclock 有什么区别?

答案要点:进程时间 = 用户 CPU 时间 + 系统 CPU 时间;times 可区分用户/系统时间,clock 只给总时间。

详细解答:休眠不消耗 CPU、不计入进程时间,要与「时间总和」区分。times(&tms) 返回节拍数,节拍数 / _SC_CLK_TCK 得秒;struct tms 提供 tms_utimetms_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);

追问

  1. 为什么进程时间可能远小于程序运行时间?(进程休眠/阻塞期间不占 CPU)
  2. clock() 能区分用户态和内核态吗?(不能)

Q6.5 随机数、休眠、堆内存与 /proc 各要注意什么?

答案要点:随机数用 rand/srand(伪随机,种子常用 time(NULL));休眠 sleep/usleep/nanosleep;堆内存 malloc/freecalloc 置 0);/proc 是虚拟文件系统,用 open+readcat 读取。

详细解答

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(内核版本)、cpuinfomeminfostatuptimecmdline 等。

追问

  1. 进程退出内核会回收内存,为什么还要 free?(避免阶段性命周期内的内存泄漏,提升可维护性)
  2. sleep 和裸机 for 循环延时的本质区别?(sleep 主动让出 CPU,后者一直占用 CPU 空转)

内容来源:《I.MX6U嵌入式Linux C应用编程指南》第一章~第七章