Linux/Golang/glibC系统调用

linux,golang,glibc · 浏览次数 : 0

小编点评

**代码分析** 您对 glibc 代码的分析非常详细,并对代码的各个部分进行了细致的讨论。代码简洁易懂,但也存在一些复杂的地方,需要进行进一步分析。 **代码优点** * 使用宏和链接简化了代码结构,提高了代码可读性。 * 通过编译产物仓库,可以轻松找到所需的库文件。 * 代码利用了对 x86 平台中断号的了解,提高了代码可移植性。 **代码缺点** * 代码使用了许多弱定义和宏,这可能导致编译错误和运行时错误。 * 代码使用很多嵌套的宏,难以理解其含义。 * 代码缺乏代码注释,这可能会导致代码维护困难。 **代码建议** * 使用代码生成工具,例如 ANTLR 或 LLVM,可以自动生成代码,并减少代码中的宏和链接。 * 使用 C 语言的编译器优化选项,例如 `-O3`,可以优化代码并减少运行时开销。 * 添加代码注释,以提高代码可读性和维护性。 * 使用现代代码风格,例如使用类型注释和多态类型。 **批评** * 代码中的 `_IO_fread` 宏的别名 `fread` 可能导致命名冲突,因为它与标准库 `fread` 函数相同。 * 代码中使用了 `inline` 关键字,这可能影响编译器的优化,并可能导致代码性能下降。 **总结** 代码分析非常全面,对 glibc 代码进行了深入探讨。虽然代码简洁易懂,但也存在一些复杂性,需要进行优化才能进一步提高代码可读性和性能。

正文

Linux/Golang/glibC系统调用

本文主要通过分析Linux环境下Golang的系统调用,以此阐明整个流程

有时候涉略过多,反而遭到质疑~,写点文章证明自己实力也好

Golang系统调用

找个函数来分析
https://pkg.go.dev/os/exec#Cmd.Wait

源码文件在src/os目录下的: exec.go -> exec_unix.go -> pidfd_linux.go
https://github.com/golang/go/blob/2f6426834c150c37cdb1330b48e9903963d4329c/src/os/exec.go#L134
go/src/os
/exec.go

go/src/os
/exec_unix.go

go/src/os
/pidfd_linux.go

往下是系统调用: src/syscall目录的 syscall_linux.go -> ``
go/src/syscall
/syscall_linux.go

image

runtime层的:src/internal/runtime/syscall/syscall_linux.go,如下图,可以看见Sysacll6只有声明没有函数体,是个外部声明。
src/internal/runtime/syscall/syscall_linux.go

其函数体内容实际上位于同目录下的 .s 汇编文件,与编译时采用的架构工具链相关。

by the way: 这里的语法是Golang汇编,属于Plan9分支。
golang汇编参考资料:

  1. 官网资料 https://go.dev/doc/asm
  2. 简洁概述 https://hopehook.com/post/golang_assembly/

总结:Golang直接了当地使用汇编实现了系统调用(软中断号),而不需要再通过 libc 去调用系统调用库。这样的好处是不需要考虑 glibc 繁杂沉重的兼容性方案。

Linux 定义的系统调用表

本地审计工具:ausyscall --dump

Linux系统调用

内核实现

通过软中断陷入内核态/特权模式
和STM32 ARM核心一样,都是由一个异常向量表描述中断对应的Handler地址,软硬中断也是一样。
系统调用函数在 include/linux/syscalls.h中定义

我们拿asmlinkage long sys_openat(int dfd, const char __user *filename, int flags, umode_t mode); 来分析

这里使用了汇编链接,它和上文提到的tbl系统调用表有关。我们拿x86/i386分析,arch/x86/entry/syscalls/syscall_32.tbl
32.tbl
中断号 295 架构i386即传统32位x86 sys_openat 是其回调函数/软中断Handler

linux/include/uapi/asm-generic/unistd.h

其实现位于 arch/处理器架构/include/之下
可以在 arch/x86 下搜索 openat

关于内核的系统调用这部分,本人会在再出一个文章。

Glibc 系统调用库

注意:Glibc属于库,不属于内核,是根文件系统的一部分。
我们在应用态陷入内核态,使用的c库里的open()等等函数,最后都是链式调用到了syscall()类的系统调用函数。

看glibc的源码,就会发现弯弯绕绕,最后是调用到

作用是将参数写入寄存器,让SoC自己触发软中断,根据Linux内核注册的软中断号执行对应地址段的函数,也就是我们常在STM32里注册定义的中断的handle函数。

Linux应用态到内核态例子

在线阅读代码:

  1. https://elixir.bootlin.com/glibc/glibc-2.29/source/include/errno.h#L37
  2. https://codebrowser.dev/glibc/glibc/io/read.c.html
  3. 带了编译产物的仓库 https://github.com/bminor/glibc/tree/a81cdde1cb9d514fc8f014ddf21771c96ff2c182
    这些在线网站都不错,但为了高亮,所以我截图放了github的

我们在应用层调用 系统库的 fread()函数
其链接到glibc库的 libio/iofread.c
image

其中第44行可见其为 _IO_fread 声明了weak弱链接别名 fread,有关别名表可见编译产物如sysdeps/unix/syscalls.list
做了一些预操作之后,调用libio/libio.h 声明的 libio/genops.c:_IO_sgetn
image
宏定义 libio/libioP.h
image
ps: JUMP2代表两个参数

image
展开宏
image
展开宏
image

展开宏

image
展开宏
image

结构体
image

也就说调用了 FP.__xsgetn(FP, DATA, N) ,展开差不多是

struct _IO_FILE_plus *THIS;
THIS->vtable->__xsgetn; 即_IO_xsgetn_t类型函数指针

即THIS/file对象的函数地址 size_t __xsgetn (FILE *FP, void *DATA, size_t N);

初始化位于
https://codebrowser.dev/glibc/glibc/libio/tst-vtables-common.c.html#jumps

相关的计数器
image

再看另一个,我们常用的fopen
fopen

_IO_file_fopen

compat_symbol (libc, _IO_old_file_fopen, _IO_file_fopen, GLIBC_2_0);

_IO_old_file_fopen

#define __open open

这里定向到了 open, 我们需要通过编译产物 sysdeps/unix/syscalls.list 找到其链接段
image

可在 io/open.c 找到函数 __libc_open 位于
image
由于弱定义,所以被以下覆盖 sysdeps/unix/sysv/linux/open.c

image
关键在于第43行的SYSCALL_CANCEL,其中的宏
image
展开宏INLINE_SYSCALL_CALL
image
展开宏
image
展开宏
image
展开宏
image

展开为

//展开
__INLINE_SYSCALL4(openat, AT_FDCWD, file, oflag, mode)
//继续展开为
__INLINE_SYSCALL(openat, 4, AT_FDCWD, file, oflag, mode)

image

//x86
#define SYS_ify(syscall_name)	__NR_##syscall_name

#define INTERNAL_SYSCALL(name, nr, args...)				\
	internal_syscall##nr (SYS_ify (name), args)
//aarch64即 arm64
# define INTERNAL_SYSCALL_AARCH64(name, nr, args...)		\
	INTERNAL_SYSCALL_RAW(__ARM_NR_##name, nr, args)

x86的展开为 internal_syscall4 (__NR_openat, AT_FDCWD, file, oflag, mode)
可在 sysdeps/unix/sysv/linux/sh/arch-syscall.h 找到,其中断号为 295
image

对应openat的x86/i386中断号,刚好就是295,源码分析完全正确!每种平台的中断号都不一样,但是这样分析是正确的。
体验一下GNU宏地狱吧!

而ARM64的就高明得多,直接通过asm汇编指令写寄存器跳转执行__libc_do_syscall完成
image
glibc/sysdeps/unix/sysv/linux/arm/libc-do-syscall.S
image

总之是一个系统调用,等价于 openat(AT_FDCWD, file, oflag, mode);

总结:sysdeps是系统调用的实现,向上屏蔽细节,但是封装的过程用于一堆条件宏,根本没办法用代码分析工具,也难以调试。
对GNU LIBC代码的个人拙见:
好处:节省空间,较好的运行速度。
坏处:作为计算机世界的底层支持,这样还不如在编译器优化阶段下功夫,过多的黑魔法必然写出难以理解的代码,牵一发动全身,没有人愿意去改这堆疯狂嵌套的代码。作为新兴语言爱好者的我,始终认为程序要少点黑魔法,简洁直接才是最优解,剩下的东西都应该交给编译器,何况系统调用的耗时从来就不在这里,主要性能影响都是在内核态用户态切换的时候,并不在c库本身。

GNU的代码向来很难读,glibc更是个寄吧,各种宏和硬链接乱飞,有再好的代码阅读工具也难找出来。
但要说来,说到底还是c语言/链接器的设计缺陷,没办法更好的实现动态表和静态表。(多态组合、编译期间的函数静态段的多分支链接)

微软的代码是框架难以理解,因为他们也不给出架构图和代码结构的...,,而GNU的代码是宏和链接难以理解。

最后

请指正批评!感谢阅读。

与Linux/Golang/glibC系统调用相似的内容:

Linux/Golang/glibC系统调用

Linux/Golang/glibC系统调用 本文主要通过分析Linux环境下Golang的系统调用,以此阐明整个流程 有时候涉略过多,反而遭到质疑~,写点文章证明自己实力也好 Golang系统调用 找个函数来分析 https://pkg.go.dev/os/exec#Cmd.Wait 源码文件在s

[转帖]goproxy 使用说明

Go 版本要求 建议您使用 Go 1.13 及以上版本, 可以在这里下载最新的 Go 稳定版本。 配置 Goproxy 环境变量 Bash (Linux or macOS) export GOPROXY=https://proxy.golang.com.cn,direct 复制 PowerShell

Linux磁盘管理

磁盘管理 【1】、Linux常用的分区格式 在新增磁盘后要对其进行分区,分区后才可以去存储数据 MBR分区格式:比较古老的分区格式,只能划分4个主分区,如果四个分区划分完成后,硬盘空间还有剩余,那剩余的分区也不能够使用。后来新增加扩展分区(容器)功能,可在扩展分区内划分出更多的逻辑分区,最大支持2.

Linux软件包管理

软件包管理 【1】、Linux软件类型 开源软件 软件源代码开放,供用户免费学习,允许用户二次开发,用户使用放心,后期如果开发者不再进行维护,会有其他人进行维护 闭源软件 软件代码不公开发布,无法二次开发,后期开发者如果不进行维护损失很大 【2】、开源软件包类型 源码包 优点: 可以看到软件源代码,

面试题:Linux 系统基础提问 (一)

Linux系统中如何管理用户和组? Linux系统中用户和组的管理通常包括以下几个方面: 1、创建用户和组: 使用useradd和groupadd命令创建新用户和新组。 2、修改用户和组信息: 使用usermod和groupmod命令来修改用户和组的信息。 3、删除用户和组: 使用userdel和g

Linux管道符

十五、管道符 管道符和grep命令结合的是最多的 管道符的标准定义: 管道是一种通信机制,常用语进程之间的通信。它表现出来的形式:将前一个的标准输出(stdout)作为后面命令的标准输入(stdin) 利用grep和管道符来查看用户信息 用户信息存储在 /etc/passwd中 我们自己创建的用户的

Linux的访问权限详解

题目 解读访问权限 rw-r--r--分别代表什么东西 r:代表可读 w:可写 e:可执行 方便起见进行拆分 rw- 代表文件所属用户的权限 r-- 代表同组用户的权限 r-- 代表其他用户的权限 同时我们可以用2进制来表示: r:4 w:2 e:1 也即是3位二进制数则可以表示 chmod 命令

Linux内存不够了?看看如何开启虚拟内存增加内存使用量

1、为什么要使用虚拟内存 当我们没有多余的钱去购买大内存的云服务器时,但是当前服务器里面的软件和程序运行的比较多导致内存不够用了。这个时候可以通过增加虚拟内存来扩大内存容量。但是在启用虚拟内存时,需要仔细考虑系统的实际需求和硬件配置,以及权衡虚拟内存的优缺点,考虑好利弊后在开启虚拟内存。 2、什么是

保姆教程系列:小白也能看懂的 Linux 挂载磁盘实操

!!!是的没错,胖友们,保姆教程系列又更新了!!! @目录前言简介一、磁盘分区二、文件系统三、实际操作1. 使用lsblk命令查看新加入的磁盘信息2. 使用fdisk或者cfdisk分区新磁盘,并将分区标记为Linux文件系统类型(83)3. 格式化新分区,使用mkfs命令4. 创建挂载目录,使用m

Linux系统中如何查看磁盘情况

Linux不像windows系统那样方便的图形界面,特别是作为服务器使用的时候,只有命令行可以使用。 我有个云服务器平时用来做一些数据分享用的,最近想看看磁盘和其中文件的占用情况,于是搜索并学习了一些查看磁盘空间信息的命令,命令虽然简单,但对我自己来说还是有些新的东西值得记录。 1. df 首先,登