Chinaunix首页 | 论坛 | 博客
  • 博客访问: 265549
  • 博文数量: 107
  • 博客积分: 305
  • 博客等级: 二等列兵
  • 技术积分: 417
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-22 09:42
文章分类

全部博文(107)

文章存档

2014年(3)

2013年(41)

2012年(34)

2011年(28)

2008年(1)

分类: LINUX

2013-08-19 10:12:13

现代操作系统中,内核提供了用户进程和内核进程交互的一组接口,让app可以受限的访问硬件资源,提供进程间通信机制,实际上主要是为了保证系统稳定可靠,避免应用程序do whatever they want.

1.与内核通信

系统调用在用户空间进程和硬件设备之间添加了一个中间层,主要作用:

①为用户空间提供了一种硬件的抽象接口;

②保证了系统的稳定和安全,可以给予权限,用户对访问进行裁决;

③每个进程都运行在虚拟系统中;

Linux中,系统调用是用户空间访问内核的唯一手段;除异常和陷入外,是内核唯一的合法入口;实际上像设备文件和/proc之类的方式,也是通过系统调用进行访问的。

 

2  API、POSIX和C库

应用程序通过在用户空间实现的应用编程接口(API),而不是直接通过系统调用来编程。

因为API实际上不需要和系统调用对应,一个API可以实现成一个系统调用,也可以通过调用多个系统调用来实现,也可以完全不用。POSIXAPIC库及系统调用关系如下

程序员只跟API打交道,内核只跟系统调用打交道;即内核提供机制,API提供策略。

C库实现了大部分的POSIX标准API.

 

3.系统调用

系统调用一般用返回0来表示成功,返回负数表明错误,错误码写入errno全局变量,peeror()库函数可以把错误码转变成错误字符串.

举一例,获取进程ID号的系统调用getpid()

点击(此处)折叠或打开

  1. asmlinkage long sys_getpid(void)
  2. {
  3.     return current->tgid;
  4. }

asmlinkage限定词是编译器指令,告知编译器仅从堆栈中提取函数的参数;

②内核返回long,用户空间返回int,是为了保证32/64位系统兼容;

get_pid在内核被定义为sys_getpid(),内核对系统调用都是如此定义的;

(1)系统调用号

Linux中,每个系统调用号被赋予一个唯一的系统调用号,进程不会提及系统调用名称,而是用系统调用号来关联具体的系统调用。

一个系统调用号一旦被分配,不能随意变更;用sys_ni_syscall()来补缺已经删除的调用号;

系统调用号保持在unsigned long sys_call_table[NR_syscalls];

(2)系统调用的性能

Linux上下文切换时间很短,进出内核都被优化的简洁高效;系统调用处理程序和每个系统调用本身都非常简洁,所以Linux系统调用比许多其他操作系统都执行的快。

(3)系统调用处理程序

通过软中断引发一个异常,促使系统切换到内核态,执行异常处理程序代码;这个异常处理程序就是系统调用处理程序system_call()

①找到指定的系统调用

X86上是通过eax把系统调用号传给内核,system_call()通过查找sys_call_table[]找到对应的系统调用

②参数传递

Ebx,ecx,edx,esiedi依次存放前五个参数,若需要六个以上参数,用单独寄存器指向这些参数在用户空间地址的指针。通过eax存放返回值。

 

4.系统调用的实现

(1)决定用途,每个系统调用功能应该单一明确,不提倡多用途系统调用。系统调用参数,返回值和错误码都要明确,不要对机器字节长度和字节序做假设。

(2)参数验证:内核必须保证

①指向用户空间内存的指针,内核不能直接访问;

②指针指向的内存在用户进程空间里,内核不能读其他进程空间;

③内存不能绕过访问限制:可读内存标记为可读,可写标记为可写,可执行标记为可执行

内核用copy_to_user()copy_from_user()来从用户空间读写数据,都是把第二个参数指定位置数据传送到第一个参数指定位置,长度由第三个参数决定。执行失败,返回未传送字节,成功返回0copy_to_user()copy_from_user()都可能引起休眠。

④检查权能,针对合法权限,比如if (!capable(CAP_SYS_BOOT))      return –EPERM;

(3)内核执行系统调用时处于进程上下文,current指针指向引发系统调用的那个进程。能够休眠,所以系统调用必须是可重入的

(4)往系统添加一个系统调用的一个简单实例

①添加系统调用名字函数名字sys_mytest,一般在calls.S或者entry.S

/* 320 */ .long       sys_get_mempolicy

              .long       sys_set_mempolicy

              .long   sys_mytest

②在unistd.h添加系统调用号,322

#define __NR_get_mempolicy             (__NR_SYSCALL_BASE+320)

#define __NR_set_mempolicy             (__NR_SYSCALL_BASE+321)

#define __NR_mytest                   (__NR_SYSCALL_BASE+322)

③实现系统调用函数

点击(此处)折叠或打开

  1. asmlinkage long sys_mytest(struct testsys __user *buf)
  2. {
  3. #if 1
  4.     struct testsys pbuf_kernel;
  5.     copy_from_user(&pbuf_kernel,buf,sizeof(pbuf_kernel));
  6.     pbuf_kernel.cmd += 1;
  7.     pbuf_kernel.value += 2;
  8.     copy_to_user(buf,&pbuf_kernel,sizeof(pbuf_kernel));
  9. #endif
  10.     printk("---this is my test about sys_call!\r\n");
  11.     return 0;
  12. }

④在syscalls.h做系统调用函数声明

点击(此处)折叠或打开

  1. asmlinkage long sys_keyctl(int cmd, unsigned long arg2, unsigned long arg3,
  2.              unsigned long arg4, unsigned long arg5);
  3. asmlinkage long sys_mytest(struct testsys __user *buf);

app测试

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #define __NR_mytest 322
  6. struct testsys{
  7.         int cmd;
  8.         int value;
  9. };
  10. int main(void)
  11. {

  12.         struct testsys mysys;
  13.         mysys.cmd = 2;
  14.         mysys.value = 2;

  15.         //syscall(__NR_mytest);
  16.         syscall(322,&mysys);
  17.         printf("mysys.cmd:%d.\nmysys.value:%d\n",mysys.cmd,mysys.value);
  18.         return 0;
  19. }

测试结果:


5.添加系统调用

优点有

①系统调用创建容易,且使用方便;

Linux系统调用高性能显而易见

缺点是:

①需要一个系统调用号,这个需要官方分配

②系统调用被加入稳定内核固化后,接口不能改变;

③需要将系统调用分别分配到各种体系结构去(与硬件相关)

④在脚本中不容易调用系统调用,也不能从文件系统直接访问系统调用

⑤在主内核树之外很难维护

⑥如果只进行简单信息交换,系统调用大材小用了。所以尽管建立一个系统调用非常容易,但是不建议这么做,替代方法:

①实现一个设备节点,并对此实现read()write()ioctl()来进行操作

②像信号量这样的某些接口,可以用文件描述符来表示

③把增加的信息作为一个文件放在sysfs的合适位置

 

Linux尽量使系统调用简洁,事实上Linux已经是一个相对稳定并且功能已经较为完善的操作系统。

阅读(579) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~