本文是Linux系统调用专栏系列文章的第一篇,对Linux系统调用的定义、基本原理、使用方法和注意事项大概作了一个介绍,以便读者对Linux系统调用建立一个大致的印象。

什么是系统调用?

Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统调用命令在自己的应用程序中调用它们。从某种角度来看,系统调用和普通的函数调用非常相似。区别仅仅在于,系统调用由操作系统核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。二者在使用方式上也有相似之处,在下面将会提到。

随Linux核心还提供了一些C语言函数库,这些库对系统调用进行了一些包装和扩展,因为这些库函数与系统调用的关系非常紧密,所以习惯上把这些函数也称为系统调用。

 

Linux中共有多少个系统调用?

这个问题可不太好回答,就算让Linus Torvaldz本人也不见得一下子就能说清楚。

在2.4.4版内核中,狭义上的系统调用共有221个,你可以在<内核源码目录>/include/asm-i386/unistd.h中找到它们的原本,也可以通过命令"man 2 syscalls"察看它们的目录(man pages的版本一般比较老,可能有很多最新的调用都没有包含在内)。广义上的系统调用,也就是以库函数的形式实现的那些,它们的个数从来没有人统计过,这是一件吃力不讨好的活,新内核不断地在推出,每一个新内核中函数数目的变化根本就没有人在乎,至少连内核的修改者本人都不在乎,因为他们从来没有发布过一个此类的声明。

随本文一起有一份经过整理的列表,它不可能非常全面,但常见的系统调用基本都已经包含在内,那里面只有不多的一部分是你平时用得到的,本专栏将会有选择的对它们进行介绍。

 

为什么要用系统调用?

实际上,很多已经被我们习以为常的C语言标准函数,在Linux平台上的实现都是靠系统调用完成的,所以如果想对系统底层的原理作深入的了解,掌握各种系统调用是初步的要求。进一步,若想成为一名Linux下编程高手,也就是我们常说的Hacker,其标志之一也是能对各种系统调用有透彻的了解。

即使除去上面的原因,在平常的编程中你也会发现,在很多情况下,系统调用是实现你的想法的简洁有效的途径,所以有可能的话应该尽量多掌握一些系统调用,这会对你的程序设计过程带来意想不到的帮助。

 

系统调用是怎么工作的?

一般的,进程是不能访问内核的。它不能访问内核所占内存空间也不能调用内核函数。CPU硬件决定了这些(这就是为什么它被称作"保护模式")。系统调用是这些规则的一个例外。其原理是进程先用适当的值填充寄存器,然后调用一个特殊的指令,这个指令会跳到一个事先定义的内核中的一个位置(当然,这个位置是用户进程可读但是不可写的)。在Intel CPU中,这个由中断0x80实现。硬件知道一旦你跳到这个位置,你就不是在限制模式下运行的用户,而是作为操作系统的内核--所以你就可以为所欲为。

进程可以跳转到的内核位置叫做sysem_call。这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入口地址。接着,就调用函数,等返回后,做一些系统检查,最后返回到进程(或到其他进程,如果这个进程时间用尽)。如果你希望读这段代码,它在<内核源码目录>/kernel/entry.S,Entry(system_call)的下一行。

 

如何使用系统调用?

先来看一个例子:

#include<linux/unistd.h> /*定义宏_syscall1*/
#include<time.h> /*定义类型time_t*/
_syscall1(time_t,time,time_t *,tloc) /*宏,展开后得到time()函数的原型*/
main()
{
time_t the_time;
the_time=time((time_t *)0); /*调用time系统调用*/
printf("The time is %ld\n",the_time);
}
系统调用time返回从格林尼治时间1970年1月1日0:00开始到现在的秒数。
这是最标准的系统调用的形式,宏_syscall1()展开来得到一个函数原型,稍后我会作详细解释。但事实上,如果把程序改成下面的样子,程序也可以运行得同样的结果。
#include<time.h>
main()
{
time_t the_time;
the_time=time((time_t *)0); /*调用time系统调用*/
printf("The time is %ld\n",the_time);
}

  

这是因为在time.h中实际上已经用库函数的形式实现了time这个系统调用,替我们省掉了调用_syscall1宏展开得到函数原型这一步。

大多数系统调用都在各种C语言函数库中有所实现,所以在一般情况下,我们都可以像调用普通的库函数那样调用系统调用,只在极个别的情况下,我们才有机会用到_syscall*()这几个宏。

 

_syscall*()是什么?

在unistd.h里定义了7个宏,分别是

_syscall0(type,name)
_syscall1(type,name,type1,arg1)
_syscall2(type,name,type1,arg1,type2,arg2)
_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)
_syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5)
_syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)

  

它们看起来似乎不太像宏,但其实质和 
#define MAXSIZE 100 
里面的MAXSIZE没有任何区别。

它们的作用是形成相应的系统调用函数原型,供我们在程序中调用。我们很容易就能发现规律,_syscall后面的数字和typeN,argN的数目一样多。事实上,_syscall后面跟的数字指明了展开后形成函数的参数的个数,让我们看一个实例,就是刚刚用过的time系统调用:

_syscall1(time_t,time,time_t *,tloc)

  

展开后的情形是这样:

time_t   time(time_t *   tloc)
{
long __res;
__asm__ volatile("int $0x80" : "=a" (__res) : "0" (13),"b" ((long)(tloc)));
do {
if ((unsigned long)(__res) >= (unsigned long)(-125)) {
errno = -(__res);
__res = -1;
}
return (time_t) (__res);
} while (0) ;
}

  

可以看出,_syscall1(time_t,time,time_t *,tloc)展开成一个名为time的函数,原参数time_t就是函数的返回类型,原参数time_t *和tloc分别构成新函数的参数。事实上,程序中用到的time函数的原型就是它。

 

errno是什么?

为防止和正常的返回值混淆,系统调用并不直接返回错误码,而是将错误码放入一个名为errno的全局变量中。如果一个系统调用失败,你可以读出errno的值来确定问题所在。

errno不同数值所代表的错误消息定义在errno.h中,你也可以通过命令"man 3 errno"来察看它们。

需要注意的是,errno的值只在函数发生错误时设置,如果函数不发生错误,errno的值就无定义,并不会被置为0。另外,在处理errno前最好先把它的值存入另一个变量,因为在错误处理过程中,即使像printf()这样的函数出错时也会改变errno的值。

 

系统调用兼容性好吗?

很遗憾,答案是--不好。但这决不意味着你的程序会三天两头的导致系统崩溃,因为系统调用是Linux的内核提供的,所以它们工作起来非常稳定,对于此点无需丝毫怀疑,在绝大多数的情况下,系统调用要比你自己编写的代码可靠而高效的多。

但是,在Linux的各版本内核之间,系统调用的兼容性表现得并不像想象那么好,这是由Linux本身的性质决定的。Linux是一群程序设计高手利用业余时间开发出来的,他们中间的大部分人没有把Linux当成一个严肃的商业软件,(现在的情况有些不同了,随着Linux商业公司和以Linux为生的人的增长,不少人的脑筋发生了变化。)结果就是,如果新的方案在效率和兼容性上发生了矛盾,他们往往舍弃兼容性而追求效率,就这样,如果他们认为某个系统调用实现的比较糟糕,他们就会毫不犹豫的作出修改,有些时候甚至连接口也一起改掉了,更可怕的是,很多时候,他们对自己的修改连个招呼也不打,在任何文档里都找不到关于修改的提示。这样,每当新内核推出的时候,很可能都会悄悄的更新一些系统调用,用户编制的应用程序也会跟着出错。

说到这里,你是不是感觉前途一片昏暗呢?呵呵,不用太紧张,如前面所说,随着越来越多的人把Linux当成自己的饭碗,不兼容的情况也越来越罕见。从2.2版本以后的Linux内核已经非常稳定了,不过尽管如此,你还是有必要在每个新内核推出之后,对自己的应用程序进行兼容性测试,以防止意外的发生。

 

该如何学习使用Linux系统调用呢?

你可以用"man 2 系统调用名称"的命令来查看各条系统调用的介绍,但这首先要求你要有不错的英语基础,其次还得有一定的程序设计和系统编程的功底,man pages不会涉及太多的应用细节,因为它只是一个手册而非教程。如果man pages所提供的东西不能使你感到非常满意,那就跟我来吧,本专栏将向你展示Linux系统调用编程的无穷魅力。

虽然本专栏并非异常高深的技术文章,但是还对读者有两点小小的要求:1)读者必须有一定的C语言编程经验,本专栏不会在语言细节上过分纠缠;2)读者必须有一定的Linux使用经验,本专栏也不打算在Linux应用上大动干戈。举一个小小的测试标准,如果你能完全看懂本文从开头到这里所讲的东西,你就合格了。收拾好行囊,准备出发吧!

附: 《Linux系统调用列表》

参考资料

  • Linux man pages
  • The Linux Programmer's Guider, Sven Goldt & Sven van der Meer & Scott Burkett & Matt Welsh,1995
  • 计算机操作系统,汤子瀛 哲凤屏 汤小丹,西安电子科技大学出版社,2000
  • IBM系统调用跟我学

[Linux]系统调用理解(1)的更多相关文章

  1. [Linux]系统调用理解(3)

    本文介绍了Linux下的进程的一些概念,并着重讲解了与Linux进程管理相关的重要系统调用wait,waitpid和exec函数族,辅助一些例程说明了它们的特点和使用方法. 1.7 背景 在前面的文章 ...

  2. [Linux]系统调用理解(2)

    本文介绍了Linux下的进程概念,并着重讲解了与Linux进程管理相关的4个重要系统调用getpid,fork,exit和_exit,辅助一些例程说明了它们的特点和使用方法. 关于进程的一些必要知识 ...

  3. [Linux]系统调用理解(4)

    这是本专栏中进程相关的系统调用的最后一篇,用2个实例演示了以往学习的内容.其一是Mini Shell,仿常用的Bash而做,但对其作了大大简化:其二是一个Daemon程序,可以使读者一窥服务器编程的端 ...

  4. 理解Linux系统调用

    目录 1.什么是系统调用 2.linux的系统调用 3.linux系统调用实现 1.什么是系统调用 系统调用,指的是操作系统提供给用户程序调用的一组特殊接口,用户程序可以根据这组接口获得操作系统内核的 ...

  5. [Linux]Linux系统调用列表

    本文列出了大部分常见的Linux系统调用,并附有简要中文说明. 以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数.这可能是你在互联网上所能看到的唯一一篇中文注释的 ...

  6. 别出心裁的Linux系统调用学习法

    别出心裁的Linux系统调用学习法 操作系统与系统调用 操作系统(Operating System,简称OS)是计算机中最重要的系统软件,是这样的一组系统程序的集成:这些系统程序在用户对计算机的使用中 ...

  7. 关于Linux系统调用,内核函数【转】

    转自:http://blog.csdn.net/ubuntulover/article/details/5988220 早上听人说到某个程序的一部分是内核态,另一部分是用户态,需要怎么怎么.当时突然想 ...

  8. Linux系统调用(转载)

    目录: 1. Linux系统调用原理 2. 系统调用的实现 3. Linux系统调用分类及列表 4.系统调用.用户编程接口(API).系统命令和内核函数的关系 5. Linux系统调用实例 6. Li ...

  9. UNIX环境高级编程——Linux系统调用列表

    以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数.这可能是你在互联网上所能看到的唯一一篇中文注释的Linux系统调用列表,即使是简单的字母序英文列表,能做到这么完 ...

随机推荐

  1. 基于thrift的微服务框架

    前一阵开源过一个基于spring-boot的rest微服务框架,今天再来一篇基于thrift的微服务加框,thrift是啥就不多了,大家自行百度或参考我之前介绍thrift的文章, thrift不仅支 ...

  2. Jquery自定义插件之$.extend()、$.fn和$.fn.extend()

    jquery插件的种类: 1.对象级别的插件开发,即给jQuery对象添加方法,封装对象方法的插件,如:parent().appendTo() 2.一种是类级别的插件开发,即给jQuery添加新的全局 ...

  3. 添加native service

    原文地址:http://blog.csdn.net/zhx6044/article/details/47342227 Native Service 其实就是一个 linux 守护进程,提供一些服务,不 ...

  4. JVM_垃圾回收串行、并行、并发算法(总结)

    一.串行 JDK1.5前的默认算法 缺点是只有一个线程,执行垃圾回收时程序停止的时间比较长 语法 -XX:+UseSerialGC 新生代.老年代使用串行回收 新生代复制算法 老年代标记-压缩 示例图 ...

  5. Adapter的封装之路

    原文:Adapter的封装之路 一.几种常见列表效果: 假如要用RecyclerView实现下面的几种效果,你会如何实现呢? 效果1:单布局效果   效果2:多布局效果 有多种Item布局   效果3 ...

  6. ORB-SLAM(二)性能

    ORB-SLAM程序提供了运行Monocular.Stereo和RGBD数据的程序.编译成功后,可以通过运行TUM的标准数据来验证程序是否成功.如果想自己测试一些数据,可以通过OpenCV提供的接口调 ...

  7. JQuery slidebox实现图片轮播

    jQuery图片轮播(焦点图)插件jquery.slideBox,简单设置下参数就可以多个多种动画效果,左右,上下,速度,还可指定默认显示第N张,点击的按钮在现代浏览中可以实现圆形或圆角效果,插件代码 ...

  8. 《CSS权威指南》基础复习+查漏补缺

    前几天被朋友问到几个CSS问题,讲道理么,接触CSS是从大一开始的,也算有3年半了,总是觉得自己对css算是熟悉的了.然而还是被几个问题弄的"一脸懵逼"... 然后又是刚入职新公司 ...

  9. TypeScript Writing .d.ts files(编写声明文件)

    当使用扩展的JavaScript库或者插件API的时候,将需要使用声明文件(.d.ts)来描述库的类型.本文内容将包括如何编写声明文件相关的一些高级概念,然后用一些例子来展示如何将各式各样的概念与声明 ...

  10. CRM(客户关系管理)

    CRM最初是由Gartner Group提出的. CRM定义:"客户关系管理(CRM),是代表增进赢利.收入和客户满意度而设计的,企业范围的商业战略." 我们可以看出,Gartne ...