系统调用(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_opensys_mmapsys_writesys_close 等等系统调用;
  • 另外,多个 API 也可能只对应同一个系统调用,如glibc 下实现的 malloccallocfree 等函数用来分配和释放内存,都利用了内核的 sys_brk 的系统调用。

举例来说,我们通过 glibc 提供的chmod 函数来改变文件 etc/passwd 的属性为 444:

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h> 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,说明系统调用失败,错误码为1,在 /usr/include/asm-generic/errno-base.h 文件中有如下错误代码说明:

#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, ...)
  • sysno 是系统调用号,每个系统调用都有唯一的系统调用号来标识。在 sys/syscall.h 中有所有可能的系统调用号的宏定义。
  • ... 为剩余可变长的参数,为系统调用所带的参数,根据系统调用的不同,可带0~5个不等的参数,如果超过特定系统调用能带的参数,多余的参数被忽略。
  • 返回值 该函数返回值为特定系统调用的返回值,在系统调用成功之后你可以将该返回值转化为特定的类型,如果系统调用失败则返回 -1,错误代码存放在 errno 中。

还以上面修改 /etc/passwd 文件的属性为例,这次使用 syscall 直接调用:

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h> 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 <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <errno.h> 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 edtion
  • The GNU C Library Reference Manual, for version 2.18

Linux 下系统调用的三种方法的更多相关文章

  1. Linux下文件的三种时间戳

    Linux下文件的三种时间标记 三种时间对应关系表 column column column 访问时间 Access atime 修改时间 Modify mtime 状态改动时间 Change cti ...

  2. Linux添加系统调用的两种方法

    前言 系统调用的基本原理 系统调用其实就是函数调用,只不过调用的是内核态的函数,但是我们知道,用户态是不能随意调用内核态的函数的,所以采用软中断的方式从用户态陷入到内核态.在内核中通过软中断0X80, ...

  3. Linux下SVN的三种备份方式

    原文链接:http://blog.csdn.net/windone0109/article/details/4040772 (本文例子基于FreeBSD/Linux实现,windows环境请自己做出相 ...

  4. Linux下文件的三种时间标记:访问时间、修改时间、状态改动时间 (转载)

    在windows下,一个文件有:创建时间.修改时间.访问时间. 而在Linux下,一个文件也有三种时间,分别是:访问时间.修改时间.状态改动时间. 两者有此不同,在Linux下没有创建时间的概念,也就 ...

  5. 【转】 Linux 线程同步的三种方法

    线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点.linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 通过锁机制实现线程间的 ...

  6. Linux下文件的三种时间标记(atime ctime mtime)

    在windows下,一个文件有:创建时间.修改时间.访问时间. 在Linux下,一个文件有:状态改动时间.修改时间.访问时间. 1)查看文件(或文件夹)的三种时间标记 (stat 命令) Access ...

  7. linux下主流的三种远程技术

    远程登录操作对于租用服务器的用户来说都不陌生.特别是租用国外服务器的用户来说,更是家常便饭.通过远程登录操作,即使我们人在深圳,也能无差别的操作远在美国的服务器.而对于linux系统下的服务器,目前主 ...

  8. linux设置变量的三种方法

    1在/etc/profile文件中添加变量对所有用户生效(永久的) 用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是“永久生效”. 例如:编辑/etc/ ...

  9. linux虚拟主机的三种方法

    虚拟主机虚拟主机是将一台(或者一组)服务器的资源(系统资源.网络带宽.存储空间等)按照一定的比例分割成若干相对独立的“小主机”的技术.每一台这样的“小主机”在功能上都可以实现WWW.FTP.Mail等 ...

随机推荐

  1. NPOI读取Excel帮助类,支持xls与xlsx,实现公式解析,空行的处理

    NPOI读取Excel(2003或者2010)返回DataTable.支持公式解析,空行处理. /// <summary>读取excel /// 默认第一行为表头 /// </sum ...

  2. 比较牛X的互联网公司都有哪些作死的行为

    以下为近乎家的小近吐血整理: 1流氓行为 臭表碾说的就是你们!   百度 还有这种伪造网页弹窗: 360 不经同意,也不弹窗提醒,直接给我们安装推广软件.比较典型的是 腾讯 腾讯一直走在行业最前端,买 ...

  3. Lind.DDD.ILogicDeleteBehavor~逻辑删除的实现

    回到目录 关于逻辑删除 对于逻辑删除之前的做法是在实体类中加个字段,一般是status,其中一种状态是删除,当然也有其它做法,如加个bool的字段IsDeleted,这些其实都过于武断,即它在基类里加 ...

  4. Hibernate 参数设置一览表

    Hibernate 参数设置一览表 属性名 用途 hibernate.dialect 一个Hibernate Dialect类名允许Hibernate针对特定的关系数据库生成优化的SQL. 取值 fu ...

  5. gulp教程之gulp-autoprefixer<转>

    简介: 使用gulp-autoprefixer根据设置浏览器版本自动处理浏览器前缀.使用她我们可以很潇洒地写代码,不必考虑各浏览器兼容前缀.[特别是开发移动端页面时,就能充分体现它的优势.例如兼容性不 ...

  6. Java Web中乱码问题

    response.setContentType("text/html;charset=UTF-8"); 用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式.什么编码读取这个 ...

  7. React Native常用组件Image使用

    前言 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所 ...

  8. 极光推送-适配 iOS10

    //************************ iOS10 适配 **************************// //************************ 11/02/20 ...

  9. Java避免创建不必要的对象

    小Alan最近看到了<Effective Java>这本书,这本书包含的内容非常丰富,这本书我就不多介绍了,只能默默的说一句,作为一名java开发错过了这本书难免会成为一个小遗憾,所以还是 ...

  10. mysqldump:Couldn't execute 'show create table `tablename`': Table tablename' doesn't exist (1146)

    遇到了一个错误mysqldump: Couldn't execute 'show create table `CONCURRENCY_ERRORS`': Table INVOICE_OLD.CONCU ...