欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux下C库函数到系统调用函数到内核函数调用的过程

程序员文章站 2024-01-21 21:27:16
...

当我们在shell写入一个程序的时候

#include <stdio.h>

此处调用了stdio.h的C标准库,他是存在在glibc中的库函数,他里面通过一些预处理最终会调用系统调用函数,其中,系统调用函数一般是放在

#include <unistd.h>

当然,我们也可以直接写一个系统调用函数调用内核函数

ssize_t write(int fd, const void *buf, size_t count);
/*************************************************************************
	> File Name: hello.c
	> Author: liuhao
	> Mail: [email protected]
	> Created Time: Sat 19 Jun 2021 11:02:59 AM CST
 ************************************************************************/

#include<stdio.h>
#include <unistd.h>
int main() {
    printf("Hello World\n");
    write(1, "Hello World\n", 20);
    return 0;
}
➜  C ./a.out
Hello World
Hello World

以write为例,系统调用函数和内核函数有一个接口来调用内核函数sys_write,该方法会直接调用硬件(比如write,会直接写入到屏幕上进行显示)

  • open: 打开文件或设备
  • read: 从打开的文件或设备中读取数据
  • write: 向打开的文件或设备中写入数据
  • close:关闭文件或者设备
  • ioctl:把控制信息传递给设备驱动文件

具体执行讲解可见ELF可执行文件执行全过程

strace ./a.out
execve("./a.out", ["./a.out"], 0x7fffc106e0d0 /* 27 vars */) = 0
brk(NULL)                               = 0x564fe1e53000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fff87737bd0) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=102952, ...}) = 0
mmap(NULL, 102952, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0272e45000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\[email protected]\0\0\0\0\0\0\[email protected]\0\0\0\0\0\0\[email protected]\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0272e43000
pread64(3, "\6\0\0\0\4\0\0\[email protected]\0\0\0\0\0\0\[email protected]\0\0\0\0\0\0\[email protected]\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0272c51000
mprotect(0x7f0272c76000, 1847296, PROT_NONE) = 0
mmap(0x7f0272c76000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f0272c76000
mmap(0x7f0272dee000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7f0272dee000
mmap(0x7f0272e39000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f0272e39000
mmap(0x7f0272e3f000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f0272e3f000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f0272e44540) = 0
mprotect(0x7f0272e39000, 12288, PROT_READ) = 0
mprotect(0x564fe0654000, 4096, PROT_READ) = 0
mprotect(0x7f0272e8c000, 4096, PROT_READ) = 0
munmap(0x7f0272e45000, 102952)          = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
brk(NULL)                               = 0x564fe1e53000
brk(0x564fe1e74000)                     = 0x564fe1e74000
write(1, "Hello World\n", 12Hello World
)           = 12
exit_group(0)                           = ?
+++ exited with 0 +++

其中的第一个系统调用函数execve

int execve(const char *pathname, char *const argv[], char *const envp[]);

进入内核态执行sys_execve检查argv和envp、do_execve读入目标镜像文件、search_binary_handler搜索处理该二进制文件的队列、load_elf_binary检查elf文件架构并分配内存、没有动态链接库直接执行/有动态链接库进行链接write,_再次进入系统调用sys_write、把字符串写到屏幕上的进程进行等待、执行程序