linux系统调用(2)

慕课18原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

这里我们通过一个小例子来了解一下里linux中系统调用的编程方法!我们以 系统调用 uname 为例

<一> 我们先通过C语言来实现这个小程序(后面使用嵌入式汇编来实现)

第一步:我们只知道系统调用的名字为uname,却不知道这个调用是用来干什么的,那么我们通过一条指令来查看一下:

     在终端中输入:man   2  uname  回车 会得到如下图所示的介绍:

那么我们知道了uname的作用是获取当前系统内核的名字及其他相关的信息,他的用法是:uname(struct utsname *buf)

而且需要包含头文件 utsname.h ,因为他需要的参数指针是一个utsname类型的结构体,而这个结构体是在utsname.h中定义的,其完整定义为:

struct utsname {
               char sysname[];    /* Operating system name (e.g., "Linux") */
               char nodename[];   /* Name within "some implementation-defined
                                     network" */
               char release[];    /* OS release (e.g., "2.6.28") */
               char version[];    /* OS version */
               char machine[];    /* Hardware identifier */
           #ifdef _GNU_SOURCE
               char domainname[]; /* NIS or YP domain name */
           #endif
           };

这里传递的参数是一个指针,所以很容易想到,这个系统调用的结果很可能就保存在这个指针所指向 的结构体中,从介绍信息中也能确认这一点!

uname有一个整型的返回值,这个值的作用是用来判断调用是否成功,当返回值 fb<0 时表示调用出错了(比如参数不正确等),fb= 0 表示调用成功。

                  linux系统调用(2)_第1张图片

第二步:   知道了uname的功能和用法之后,我们就用起来!下面给一个小的demo    

程序和简单,先通过uname获取信息,然后用printf()格式化输出打印出来。

          linux系统调用(2)_第2张图片

第三步:在当前目录输入gcc -o uname_c  uname_c.c  将文件进行编译成可执行文件 uname_c

然后输入 ./uname_c 运行程序,运行结构如下:

              linux系统调用(2)_第3张图片

结果可以看到,程序按照预期打印出了内核的相关信息。


<二> 下面用嵌入式汇编来实现上面的系统调用:

第一步:按照前面的步骤,我们已经知道了,uname的作用,但是用嵌入式汇编来实现时,需要用到uname对应的终端号,所以我们来查一下:

在终端中输入: less   ***/arch/x86/include/asm/unistd_32.h: (*** 表示linux源码解压后的目录!)

然后输入  /uname  找到uname对应的记录项,(/name  为输入的内容,“ / ”也是)

 我们可以看到uname对应的中断号为 122

            linux系统调用(2)_第4张图片

第二步:我们看一段demo:

  这里为了简便,和代码的可读性,只将前面C代码中的 “ fd = uname( &unm) ” 用嵌入式汇编来重写,其余部分与C代码部分的一样。

代码如下:

      

第三步:我们来简单分析一下这段汇编代码

    asm volatile(   // asm 表示告诉下面这段代码是汇编代码;volatile 表示告诉编译器对下面这段代不进行优化


            "mov %1, %%ebx\n\t"      // %1 表示的是后面跟的 1 号参数(和数组一样,从0 开始计数所以这里是第二个参数),参数时候以“ : ”开始的那几行。所以这一行代码的意思是把 参数 u 的值送到寄存器ebx中。

            "mov $122, %%eax\n\t"   //  $122  表示常数 122 也就是uname的中断号,这一行表示将122 送到寄存器eax中,因为在
            "int $0x80 \n\t"                   //   int 表示出发一个系统中断,触发后系统转到中断向量表中的对应项所指的地址去执行,这里是跳转到第128 项所指地址去执行(该中断专门为系统调用而设),进入中断后,系统默认以寄存器eax中的值为 子中断号(上一步已经将uanme的中断号送到了eax中)


            "mov %%eax, %0"     //执行这一步的时候已经从中断中返回了,而且中断处理程序的返回值默认放在了eax中,所以这里我们又将返回值从eax中写到内存变量fd中。

            :"=m"(fd)   // 这下面两行的意思是表示的参数,“=m”表示后面的变量是在内存中,且用作输出;“m”表示的后面的变量在内存中,且是输入值。

            :"m"(u)
            );
更多信息可参考:http://blog.csdn.net/clp_csdnid/article/details/50936419

第四步:编译,运行结果

          linux系统调用(2)_第5张图片


< 三 > 总结:

所谓系统调用,就是内核提供的、功能十分强大的一系列的函数。这些系统调用是在内核中实现的,再通过一定的方式把系统调用给用户,一般都通过门(gate)陷入(trap)实现。系统调用是用户程序和内核交互的接口。
整个系统调用的过程可以总结如下:
1. 执行用户程序(如:fork)
2. 根据glibc中的函数实现,取得系统调用号并执行int $0x80产生中断。
3. 进行地址空间的转换和堆栈的切换,执行SAVE_ALL。(进行内核模式)
4. 进行中断处理,根据系统调用表调用内核函数。
5. 执行内核函数。
6. 执行RESTORE_ALL并返回用户模式

库函数是对系统调用的封装(不是所有的库函数都是),为的是解决一些公共的问题和提供统一的系统调用的接口,他和系统调用的优缺点就是:系统调用速度是明显要快于库函数(并不一定全部是,但绝大部分是),但系统调用缺乏移植性。库函数速度要慢,但解决了移植问题。这些在开发过程中要根据自己的实际情况来决定使用那一个








你可能感兴趣的:(linux系统调用(2))