Linux 下系统调用的三种方法

系统调用(system call)是操作系统为在用户态运行的进程与硬件设备(如cpu、磁盘、打印机等)进行交互提供的一组接口。当用户进程需要发生系统调用时,cpu 通过软中断切换到内核态开始执行内核系统调用函数。下面介绍linux 下三种发生系统调用的方法:
通过 glibc 提供的库函数glibc 是 linux 下使用的开源的标准 c 库,它是 gnu 发布的 libc 库,即运行时库。glibc 为程序员提供丰富的 api(application programming interface),除了例如字符串处理、数学运算等用户态服务之外,最重要的是封装了操作系统提供的系统服务,即系统调用的封装。那么glibc提供的系统调用api与内核特定的系统调用之间的关系是什么呢?
通常情况,每个特定的系统调用对应了至少一个 glibc 封装的库函数,如系统提供的打开文件系统调用sys_open对应的是 glibc 中的open函数;其次,glibc 一个单独的 api 可能调用多个系统调用,如 glibc 提供的printf函数就会调用如sys_open、sys_mmap、sys_write、sys_close等等系统调用;另外,多个 api 也可能只对应同一个系统调用,如glibc 下实现的malloc、calloc、free等函数用来分配和释放内存,都利用了内核的sys_brk的系统调用。举例来说,我们通过 glibc 提供的chmod函数来改变文件etc/passwd的属性为 444:
#include #include #include #include int main(){ int rc; rc = chmod(/etc/passwd, 0444); if (rc == -1) fprintf(stderr, chmod failed, errno = %d\n, errno); else printf(chmod success!\n); return 0;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include
#include
#include
#include
int main()
{
        int rc;
rc = chmod(/etc/passwd, 0444);
        if (rc == -1)
                fprintf(stderr, chmod failed, errno = %d\n, errno);
        else
                printf(chmod success!\n);
        return 0;
}
在普通用户下编译运用,输出结果为:
chmod failed, errno = 1
1
2
chmod failed, errno = 1
上面系统调用返回的值为-1,说明系统调用失败,错误码为1,在/usr/include/asm-generic/errno-base.h文件中有如下错误代码说明:
#define eperm 1 /* operation not permitted */
1
2
#define eperm       1                /* operation not permitted */
即无权限进行该操作,我们以普通用户权限是无法修改 /etc/passwd 文件的属性的,结果正确。
使用 syscall 直接调用使用上面的方法有很多好处,首先你无须知道更多的细节,如 chmod 系统调用号,你只需了解 glibc 提供的 api 的原型;其次,该方法具有更好的移植性,你可以很轻松将该程序移植到其他平台,或者将 glibc 库换成其它库,程序只需做少量改动。
但有点不足是,如果 glibc 没有封装某个内核提供的系统调用时,我就没办法通过上面的方法来调用该系统调用。如我自己通过编译内核增加了一个系统调用,这时 glibc 不可能有你新增系统调用的封装 api,此时我们可以利用 glibc 提供的syscall函数直接调用。该函数定义在unistd.h头文件中,函数原型如下:
long int syscall (long int sysno, ...)
1
long int syscall (long int sysno, ...)
sysno是系统调用号,每个系统调用都有唯一的系统调用号来标识。在sys/syscall.h中有所有可能的系统调用号的宏定义。…为剩余可变长的参数,为系统调用所带的参数,根据系统调用的不同,可带0~5个不等的参数,如果超过特定系统调用能带的参数,多余的参数被忽略。返回值该函数返回值为特定系统调用的返回值,在系统调用成功之后你可以将该返回值转化为特定的类型,如果系统调用失败则返回 -1,错误代码存放在errno中。还以上面修改 /etc/passwd 文件的属性为例,这次使用 syscall 直接调用:
#include #include #include #include int main(){ int rc; rc = syscall(sys_chmod, /etc/passwd, 0444); if (rc == -1) fprintf(stderr, chmod failed, errno = %d\n, errno); else printf(chmod succeess!\n); return 0;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include
#include
#include
#include
int main()
{
        int rc;
        rc = syscall(sys_chmod, /etc/passwd, 0444);
if (rc == -1)
                fprintf(stderr, chmod failed, errno = %d\n, errno);
        else
                printf(chmod succeess!\n);
        return 0;
}
在普通用户下编译执行,输出的结果与上例相同。
通过 int 指令陷入如果我们知道系统调用的整个过程的话,应该就能知道用户态程序通过软中断指令int 0x80来陷入内核态(在intel pentium ii 又引入了sysenter指令),参数的传递是通过寄存器,eax 传递的是系统调用号,ebx、ecx、edx、esi和edi 来依次传递最多五个参数,当系统调用返回时,返回值存放在 eax 中。
仍然以上面的修改文件属性为例,将调用系统调用那段写成内联汇编代码:
#include #include #include #include int main(){ long rc; char *file_name = /etc/passwd; unsigned short mode = 0444; asm( int $0x80 : =a (rc) : 0 (sys_chmod), b ((long)file_name), c ((long)mode) ); if ((unsigned long)rc >= (unsigned long)-132) { errno = -rc; rc = -1; } if (rc == -1) fprintf(stderr, chmode failed, errno = %d\n, errno); else printf(success!\n); return 0;}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include
#include
#include
#include
int main()
{
        long rc;
        char *file_name = /etc/passwd;
        unsigned short mode = 0444;
asm(
                int $0x80
                : =a (rc)
                : 0 (sys_chmod), b ((long)file_name), c ((long)mode)
        );
if ((unsigned long)rc >= (unsigned long)-132) {
                errno = -rc;
                rc = -1;
        }
if (rc == -1)
                fprintf(stderr, chmode failed, errno = %d\n, errno);
        else
                printf(success!\n);
return 0;
}
如果 eax 寄存器存放的返回值(存放在变量 rc 中)在 -1~-132 之间,就必须要解释为出错码(在/usr/include/asm-generic/errno.h文件中定义的最大出错码为 132),这时,将错误码写入 errno 中,置系统调用返回值为 -1;否则返回的是 eax 中的值。
上面程序在 32位linux下以普通用户权限编译运行结果与前面两个相同!
参考资料
understanding the linux kernel, the 3rd edtionthe gnu c library reference manual, for version 2.18

RGB星形小夜灯的制作教程
苹果通过M2 Ultra SoC完成All in-house芯片过渡
典型单处理器安全计算机SACEM的介绍
无线通信射频器件交调失真的意义及矢网实例测量方法
在iPhone XR发布后30天内,16%的安卓用户购买了iPhone
Linux 下系统调用的三种方法
关于AR和VR中的预测性追踪定位的原理分析和应用
给iPhone供应OLED屏?富士康要缩小与竞争对手差距
谐波减速器企业创业板IPO提交注册
【LuckFox Pico Plus开发板免费试用】+开发板上手
从数据到生成式AI,是该重新思考风险的时候了
LVDS器件在汽车应用中的优势
曝AirPower已经开始生产 2019年第一季度或上市
智能家居趋势大好,网关是中枢
从ISA到PCI到PCI Express的可堆叠PC
TI推出业界首款36V轨至轨输入输出(RRIO)运算放大器
虾米音乐宣布将于 2 月 5 日关停播放器业务,App 下架、提供歌单导出、支持会员退款
Cadence IP组合和工具支持台积电新的超低功耗平台
PCB电路板的分类特点及生产工艺流程解析
如何制作老式12v充电器(四款12v充电器设计制作详解)