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 <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 下系统调用的三种方法的更多相关文章
- Linux下文件的三种时间戳
Linux下文件的三种时间标记 三种时间对应关系表 column column column 访问时间 Access atime 修改时间 Modify mtime 状态改动时间 Change cti ...
- Linux添加系统调用的两种方法
前言 系统调用的基本原理 系统调用其实就是函数调用,只不过调用的是内核态的函数,但是我们知道,用户态是不能随意调用内核态的函数的,所以采用软中断的方式从用户态陷入到内核态.在内核中通过软中断0X80, ...
- Linux下SVN的三种备份方式
原文链接:http://blog.csdn.net/windone0109/article/details/4040772 (本文例子基于FreeBSD/Linux实现,windows环境请自己做出相 ...
- Linux下文件的三种时间标记:访问时间、修改时间、状态改动时间 (转载)
在windows下,一个文件有:创建时间.修改时间.访问时间. 而在Linux下,一个文件也有三种时间,分别是:访问时间.修改时间.状态改动时间. 两者有此不同,在Linux下没有创建时间的概念,也就 ...
- 【转】 Linux 线程同步的三种方法
线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点.linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量. 一.互斥锁(mutex) 通过锁机制实现线程间的 ...
- Linux下文件的三种时间标记(atime ctime mtime)
在windows下,一个文件有:创建时间.修改时间.访问时间. 在Linux下,一个文件有:状态改动时间.修改时间.访问时间. 1)查看文件(或文件夹)的三种时间标记 (stat 命令) Access ...
- linux下主流的三种远程技术
远程登录操作对于租用服务器的用户来说都不陌生.特别是租用国外服务器的用户来说,更是家常便饭.通过远程登录操作,即使我们人在深圳,也能无差别的操作远在美国的服务器.而对于linux系统下的服务器,目前主 ...
- linux设置变量的三种方法
1在/etc/profile文件中添加变量对所有用户生效(永久的) 用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是“永久生效”. 例如:编辑/etc/ ...
- linux虚拟主机的三种方法
虚拟主机虚拟主机是将一台(或者一组)服务器的资源(系统资源.网络带宽.存储空间等)按照一定的比例分割成若干相对独立的“小主机”的技术.每一台这样的“小主机”在功能上都可以实现WWW.FTP.Mail等 ...
随机推荐
- Extjs.Button 按钮
Extjs Button 的简单使用 ,同时调用Ajax服务 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xh ...
- 如何在ASP.NET的web.config配置文件中添加MIME类型
常常有一些特殊的MIME类型是IIS中没有的,一般来说要我们自己手动添加.如果网站经常更换服务器或者网站代码是提供给多个用户使用,那么会造成网站中用到的特殊的MIME类型要经常性的在IIS上配置.这里 ...
- Concurrency in csharp (Asynchronous, Parallel, and Multithreaded Programming)
http://stephencleary.com/projects/ /// <summary> /// /// </summary> public partial class ...
- 当shiro不进入自定义realm的权限认证方法时
需要加入下面的一个bean @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorA ...
- 使用C#类向数据库添加数据的例子源码
在上一篇中,增加了sql server数据库操作类SqlOperator,用于操作sql server数据库.还有一个SqlStringHelper类,用于处理sql语句的单引号.那么这两个类怎么使用 ...
- 深入JavaScript:词法分析、连续赋值猜想
JavaScript:词法分析.连续赋值猜想 原创文章,转摘请注明出处:苏福:http://www.cnblogs.com/susufufu/p/5851642.html 深夜发文,先吐槽下博客园的编 ...
- iOS之处理不等高TableViewCell的几种方法
课题一:如何计算Cell高度 方案一:直接法(面向对象) 直接法,就是把数据布局到Cell上,然后拿到Cell最底部控件的MaxY值. 第一步:创建Cell并正确设置约束,使文字区域高度能够根据文字内 ...
- iOS APP上架过程常见问题
1.生产证书失效 2.上图中的蓝色选中部分的Provising Profile文件页需要导入,(调试证书(Developer).发布证书(distribution).还有Provising Profi ...
- Android高效计算——RenderScript(二)
3 RenderScript运行时层与反射层 3.1 RenderScript运行时层 RenderScript运行时层是指.rs代码运行时所在的层级.当对安卓项目进行编译的时候,.rs或者.rsh中 ...
- 【iOS】屏幕适配之NSLayoutConstraint
前言 如何实现一张图片在iPhone和iPad上显示不同的尺寸,我了解到一般有三种办法:直接手写代码动态添加约束:把NSLayoutConstraint关联到ViewController里再viewD ...