【Linux】系统调用那些事

本文详细介绍了系统调用的概念,它不同于API,是用户空间程序请求操作系统内核服务的方式。系统调用确保了系统稳定性和安全性,同时也屏蔽了硬件细节,提供了统一的接口。文章讨论了在Linux上进行系统调用的三种方法:POSIX函数、syscall函数和asm内嵌汇编。此外,还概述了系统调用在内核中的执行流程,涉及系统调用表和内核函数的映射。
摘要由CSDN通过智能技术生成

一、系统调用是什么

当我们提到系统调用的时候往往第一时间想到read,write这些API函数, 但是从严格意义上来说,系统调用并不等同于API,而只能说API只是进入系统调用的一种方式。为什么这样说呢?以系统调用gettid为例,这个调用用于获取当前线程的线程ID,但是在Linux上的glibc(详情见下文第三节)中却根本就没有这个API,这并不意味着没有这个系统调用,只不过你只能通过其他方式去执行。系统调用在维基百科中给出的定义是“运行在用户空间的程序向操作系统内核请求需要更高权限运行的服务”,此处的服务通常指的是磁盘、网络等资源服务。
系统调用

二、为什么需要系统调用

Linux将系统资源分为两部分:

  1. 内核态:CPU可执行特权指令,例如清内存、置时钟等;具有专用的内核态内存空间,用于运行系统级别的进程。运行在内核的系统级进程主要行使文件、进程、内存等软硬件资源管理的职责。
  2. 用户态:CPU只能执行非特权指令,例如基本的算术运算;具有专用的用户态内存空间,用于运行用户级别的进程。运行在用户态的应用程序只能访问自身内存区域的数据并进行非特权级别的运算, 若想要访问磁盘这些外围设备资源,则必须向操作系统申请然后在内核态完成。

正如第一节所说的,用户态进程向操作系统申请特权访问的这一过程称之为系统调用,这样做的优势在于:

  1. 系统的稳定性和安全性。例如,用户态进程不能随意修改一个不属于该用户且没有写权限的文件,不能任意访问内存中其他进程的数据等。
  2. 屏蔽硬件的差异和细节,提供统一的抽象接口。例如,当你需要往硬盘写文件时,你不需要关心硬盘是SSD还是HDD,怎么往扇区写数据,也不必关心它的文件系统是什么,你只需要指定文件路径和需要写入的数据即可。

三、如何进行系统调用

1.头文件和c库

作为一个操作系统上层应用的开发者,我们最关心的还是如何通过系统API去进行系统调用,因此有必要先梳理一下相关的概念。

当我们使用c语言调用API时,会引入stdio.h或者unistd.h这些头文件,这些头文件中包含了我们需要使用的函数声明。一开始的你有没有会觉得奇怪,为什么我们可以在任意的平台上导入stdio.h,而像unistd.h只能在Linux或者Unix系统中引用,却无法在Windows中直接使用?这就涉及到第一个话题——标准

我们都知道市面上主流的操作系统都由c语言编写而来,所以操作系统提供的编程接口天然就是以c语言形式提供的,虽然不同的操作系统的实现理念不尽相同,但上层应用开发者基本都会存在输入输出,文件读写等基本诉求,若是每个操作系统都对这些功能提供一套自己的API那对开发者来说就不是那么友好了。因此像C89(1989诞生,也称为ANSI C、ISO C、Standard C)这一c语言的首个标准就出现了,这些标准也就规范了头文件和API函数的声明

像C89以及后续的更新C99、C11都属于跨平台的标准,因此不同平台的不同c语言编译器都应该支持这些标准中的特性。话虽如此,但是只有像C89这类较老的标准才会得到广泛的完好支持。前文所提到的stdio.h就是C89中的一个特性,所以即使你在不同平台上,安装完编译器后都能找到这一头文件(Linux和Unix在/usr/include中,Windows在visual studio安装目录中)并且引入使用完成控制台的输入输出。

另外一类标准值得一提的就是POSIX标准,它最早诞生于1988年,甚至比C89还要早一年出现,比Linux早3年出现。它诞生的原因也是为了统一当时市面上多种Unix系统接口而制定的标准规范,像unistd.h就是其中之一。Linux作为模仿Unix编写出来的类Unix系统自然也继承了这一标准。这也是为什么在MacOS和Linux上可以引用这个头文件,而Windows则不行的原因了。

熟悉C/C++的知道头文件只是一系列函数和变量的声明,具体的实现是在库文件中以二进制形式存放的。作为C89、C11这类标准它的实现称之为libc,而在Linux/Unix平台上最通用的是GNU发行的glibc,它实现了包括C11,POSIX在内的多种标准,因此也可以看做是libc的超集。以centos7为例,glibc预装于系统的/lib64/libc.so.6位置,根据文件后缀名可以看出是以动态链接库的形式发布的。当然GNU作为自由软件的先锋,你也可以随时获取它的源码

2.三种调用方式

下面以C语言为例介绍一下几种系统调用的夯实

1)POSIX函数
这个方法是我们最常接触的方法,大部分常用系统调用的封装函数定义在unistd.h中,并在glibc中实现

#include <unistd.h>

void posix_func(){
    printf("posix func getpid: %d\n", getpid());
}

2)syscall函数
unistd头文件中还提供了一个syscall函数,利用它可以更加便捷地调用那些没被独立封装的系统调用(以下代码调用的是已独立封装的write,仅作参考)。第一个参数是系统调用号,可以查阅NR号码表/usr/include/asm/unistd_64.h,其余参数是变长参数,作为系统调用的入参传递。

#include <stdio.h>
#include <unistd.h>

void posix_syscall()
{
    char *str = "posix syscall write: ";
    // 1为write的调用号,0为标准输出,str为输出内容,最后一个是输出长度
    printf("%d\n",syscall(1, 0, str, strlen(str)));
}

3)asm
在c语言中使用asm内嵌语法同样可以做到系统调用,不过过程就比较繁琐了,而且需要你理解一些汇编的基础语法。下面这段代码运行在x86-64机器上,调用的是write,利用的syscall指令陷入内核(在一些较老的CPU中只能通过80中断),需要注意的是两种中断使用的寄存器并不相同

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
/*
(1)int $0x80
eax ebx ecx edx esi edi ebp eax
(2)syscall
rax rdi rsi rdx r10 r8 r9 rax

三个冒号的含义
:输出参数
:输入参数
:会被修改的寄存器
参数限定符
i 立即数
m 内存地址
r 寄存器

注意
%n从第一个冒号的参数开始算起,0是第一个,1是第二个以此类推
输出操作数约束应该带有一个约束修饰符”=”,指定它是输出操作数
*/
ssize_t using_asm()
{
    ssize_t rt;
    char *str = "using_asm size: ";
    long l = strlen(str);
    asm("movq %1, %%rax;"
        "movq %2, %%rdi;"
        "movq %3, %%rsi;"
        "movq %4, %%rdx;"
        "syscall;"
        : "=r"(rt)
        : "i"(1), "i"(0), "m"(str), "m"(l)
        );
    return rt;
}

四、系统调用发生了什么

  1. 一个名为 system_call 函数的地址写到了 LSTAR MSR。 我们来看下这个函数的实现,看它如何使用 rax 将执行交给系统调用的, arch/x86/kernel/entry_64.Scall *sys_call_table(,%rax,8) # XXX: rip relative

  2. int/syscall 陷入内核

  3. 根据系统调用表 取出寄存器中的系统调用号(第一列number) 找到对应的内核函数(最后一列entry point)
    https://github.com/torvalds/linux/blob/v3.10/arch/x86/syscalls/syscall_64.tbl

  4. 内核源码函数(以read为例)
    SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)

参考

https://arthurchiao.art/blog/system-call-definitive-guide-zh/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值