SerenityOS uname(2) 系统调用指南:从 utsname 结构到内核实现的完整解析
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
本文以 SerenityOS 的手册页uname(2)为核心,讲解uname系统调用如何检索当前内核信息:utsname结构体的五个字段含义、接口原型、返回值与错误语义,并结合内核源码 uname.cpp 与 C 库封装 utsname.cpp 说明每个字段的实际填充来源。读完后可掌握如何在 SerenityOS 上获取系统名、主机名、发行版本、修订号与机器架构,并理解各字段背后的内核实现机制。
接口原型与头文件
uname系统调用用于检索当前内核的信息,并将其写入调用者提供的utsname结构体。手册页给出的原型如下(见 uname(2) 手册页):
#include <sys/utsname.h> int uname(struct utsname* buf);调用时需将utsname结构体的用户态指针传入buf;该结构体由内核负责填充。
utsname 结构体:字段定义与长度上限
手册页中给出的结构体简写为五个变长成员:
struct utsname { char sysname[]; char nodename[]; char release[]; char version[]; char machine[]; };实际实现中各字段为定长数组,长度上限由UTSNAME_ENTRY_LEN宏指定。查阅 内核 POSIX 头文件 可以看到完整定义:
#define UTSNAME_ENTRY_LEN 65 struct utsname { char sysname[UTSNAME_ENTRY_LEN]; char nodename[UTSNAME_ENTRY_LEN]; char release[UTSNAME_ENTRY_LEN]; char version[UTSNAME_ENTRY_LEN]; char machine[UTSNAME_ENTRY_LEN]; };即每个字段最多容纳 65 字节(含结尾'\0',实际内容上限 64 字符)。该头文件通过 LibC 的头文件 sys/utsname.h 暴露给用户态程序,其中声明了int uname(struct utsname*);函数。
五个字段的语义为:
| 字段 | 含义 |
|---|---|
sysname | 操作系统名称(SerenityOS 固定为SerenityOS) |
nodename | 节点名,即当前主机名(hostname) |
release | 发行版本号(如1.0-dev形式的主/次版本) |
version | 构建修订信息(源码版本标识) |
machine | 机器架构类型(按编译目标架构而定) |
返回值与错误语义
- 成功时返回
0; - 失败时返回
-1,并通过errno描述错误原因。
手册页列出的错误条件为:
EFAULT:buf不是一个可写入的用户态地址(即内核无法将结果拷贝回用户内存)。
由于utsname的填充全部由内核内部完成,正常情况下调用几乎不会失败;EFAULT是唯一的典型错误路径,对应参数指针非法或不可写的场景。
内核侧实现:各字段是如何填充的
uname系统调用的内核实现位于 Kernel/Syscalls/uname.cpp,入口函数为Process::sys$uname。其执行流程与字段填充逻辑如下:
前置条件:Pledge 检查
TRY(require_promise(Pledge::stdio));从源码可以看到,SerenityOS 对uname系统调用施加了 Pledge 安全机制 约束:调用进程必须持有stdio权限承诺,否则系统调用会被拒绝。这是 SerenityOS 区别于传统 Unix 的一个特点——即便是查询类系统调用也需要显式承诺。
machine:由编译目标架构静态决定
machine字段在编译期通过预处理器确定,支持三种目标架构:
#if ARCH(X86_64) # define UNAME_MACHINE "x86_64" #elif ARCH(AARCH64) # define UNAME_MACHINE "aarch64" #elif ARCH(RISCV64) # define UNAME_MACHINE "riscv64" #else # error Unknown architecture #endif即uname -m的结果直接对应内核构建时选择的架构,未知架构会在编译期报错。
release 与 version:版本字符串的组装
release与version字段分别来自两处:
release:来自全局变量
g_version_string。它在 Process.cpp 的启动初始化阶段被创建:g_version_string = MUST(KString::formatted("{}.{}-dev", SERENITY_MAJOR_REVISION, SERENITY_MINOR_REVISION)).leak_ptr();从源码结构看,release 字段呈现为
<主版本>.<次版本>-dev的形式(如1.0-dev),其中版本号由构建系统注入的宏提供。version:来自
SERENITY_VERSION常量(对应 generate-version-file.sh 生成的版本信息),通常携带 git 提交哈希等构建修订信息。
内核在填充时对两者都做了长度截断保护:
AK::TypedTransfer<u8>::copy(reinterpret_cast<u8*>(buf.release), g_version_string->bytes().data(), min(g_version_string->length(), UTSNAME_ENTRY_LEN - 1));即无论版本字符串多长,最多只写入UTSNAME_ENTRY_LEN - 1个字节,保证不会溢出定长数组。
nodename:来自进程附着的主机名上下文
nodename(主机名)不是硬编码值,而是在调用时从当前进程附着的主机名上下文动态读取:
m_attached_hostname_context.with(& { hostname_context_ptr->buffer().with(& { auto name_length = name_buffer.representable_view().length(); VERIFY(name_length <= (UTSNAME_ENTRY_LEN - 1)); AK::TypedTransfer<char>::copy(reinterpret_cast<char*>(buf.nodename), name_buffer.representable_view().characters_without_null_termination(), name_length); buf.nodename[name_length] = '\0'; }); });从源码结构看,主机名以内核侧的共享上下文对象维护,uname调用时实时读取其当前值并显式补上结尾'\0';主机名在用户态被修改后,后续uname的结果会随之变化(修改主机名的命令行工具见 hostname(8) 手册页)。
结果写回用户空间
填充完成后通过一次用户态拷贝返回:
TRY(copy_to_user(user_buf, &buf)); return 0;copy_to_user失败(例如buf指向非法地址)正是手册页中EFAULT错误的来源。系统调用在 Process.h 中的声明为:
ErrorOr<FlatPtr> sys$uname(Userspace<utsname*>);C 库封装:用户态如何发起调用
用户态的uname()函数由 LibC 提供,实现见 Userland/Libraries/LibC/utsname.cpp:
int uname(struct utsname* buf) { int rc = syscall(SC_uname, buf); __RETURN_WITH_ERRNO(rc, rc, -1); }逻辑很直接:通过syscall(SC_uname, buf)发起系统调用,__RETURN_WITH_ERRNO宏负责在返回值为负(错误码)时设置errno并返回-1,成功时原样返回0。因此应用开发者只需#include <sys/utsname.h>并调用uname(),无需关心底层系统调用号。
配套命令:uname(1) 的选项与示例
系统调用之上的命令行工具uname用于打印内核信息,其手册页见 Base/usr/share/man/man1/uname.md。用法为:
$ uname [-s] [-n] [-r] [-v] [-m] [-a]各选项对应utsname的字段:
| 选项 | 作用 | 对应字段 |
|---|---|---|
-s | 打印系统名称 | sysname |
-n | 打印节点名(主机名) | nodename |
-r | 打印系统发行版本 | release |
-v | 打印发行版本的具体修订版本 | version |
-m | 打印机器类型 | machine |
-a | 打印以上全部信息 | 全部 |
手册页给出的示例输出:
$ uname -sm Serenity x86_64可见-s打印系统名、-m打印架构,与内核侧UNAME_MACHINE的定义一一对应。
小结与验证方式
综合手册页与源码,SerenityOS 的uname(2)调用链路为:应用调用 LibCuname()→SC_uname系统调用 →Process::sys$uname(Kernel/Syscalls/uname.cpp)→ Pledge 检查、组装utsname(sysname 固定、machine 按架构、release/version 来自构建版本、nodename 实时读自主机名上下文)→copy_to_user写回并返回 0。
在 SerenityOS 终端中,可通过uname -a验证全部字段的实际值;若修改主机名后再次调用uname -n,输出应同步变化,这正是内核侧动态读取主机名上下文的直接体现。
【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考