linux 用户空间调用kernel 的api

239 篇文章 2 订阅
147 篇文章 1 订阅

1.添加linux的系统调用

2.增加虚拟设备驱动

Linux下增加系统调用的二种方法

1.linux&系统调用的基本原理 

   linux的系统调用形式与POSIX兼容,也是一套C语言函数名的集合。然而,linux系统调用的内部实现方式却与DOC的INT 21H相似,它是经过INT 0X80H软中断进入后,再根据系统调用号分门别类地服务。 


从系统分析的角度,linux的系统调用涉及4个方面的问题。 
(1)与系统调用有关的数据结构和函数 
   函数名以“sys_”开头,后跟该系统调用的名字。例如,系统调用fork()的响应函数是sys_fork()(见kernel/fork.c),exit()的响应函数是sys_exit()(见kernel/fork.c)。 

   文件include/asm/unisted.h为每个系统调用规定了唯一的编号。假设用name表示系统调用的名称,那么系统调用号与系统调用响应函数 的关系是:以系统调用号_NR_name作为下标,可找出系统调用表sys_call_table(见arch/i386/kernel /entry.S)中对应表项的内容,它正好 是该系统调用的响应函数sys_name的入口地址。系统调 用表sys_call_table记录了各sys_name函数在表中的位 置,共190项。有了这张表,就很容易根据特定系统调用 在表中的偏移量,找到对应的系统调用响应函数的入口地址。系统调用表共256项,余下的项是可供用户自己添加的系统调用空间。 

(2)进程的系统调用命令转换为INT 0x80中断的过程 
       宏定义_syscallN()见include/asm/unisted.h)用于系统调用的格式转换和参数的传递。N取0~5之间的整数。 参数个数为N的系统调用由_syscallN()负责格式转换和参数传递。系统调用号放入EAX寄存器,启动INT 0x80 后,规定返回值送EAX寄存器。 

(3)系统调用功能模块的初始化 
   对系统调用的初始化也就是对INT 0x80的初始化。系统启动时,汇编子程序setup_idt(见arch/i386/kernel/head.S)准备了1张256项的idt表,由 start_kernel()(见 init/main.c),trap_init()(见arch/i386/kernel/traps.c)调用的C语言宏定义 set_system_gate(0x80,&system_call)(见 include/asm/system.h)设置0x80号软中断的服务程序为 system_call(见arch/i386/kernel/entry.S),system.call就是所有系统调用的总入口。 

(4)内核如何为各种系统调用服务 
       当进程需要进行系统调用时,必须以C语言函数的形式写一句系统调用命令。该命令如果已在某个头文件 中由相应的_syscallN()展开,则用户程序必须包含该文 件。当进程执行到用户程序的系统调用命令时,实际上执 行了由宏命令_syscallN()展开的函数。系统调用的参数 由各通用寄存器传递,然后执行INT 0x80,以内核态进 入入口地址system_call。 

(5)ret_from_sys_call
   以ret_from_sys_call入口的汇编程序段在linux进
程管理中起到了十分重要的作用。所有系统调用结束前以及大部分中断服务返回前,都会跳转至此处入口地址。 该段程序不仅仅为系统调用服务,它还处理中断嵌套、CPU调度、信号等事务。 


2.通过修改内核源代码添加系统调用 
   通过以上分析linux系统调用的过程, 将自己的系统调用加到内核中就是一件容易的事情。下面介绍一个实际的系统调用, 并把它加到内核中去。要增加的系统调用是:inttestsyscall(),其功能是在控制终端屏幕上显示hello world, 执行成功后返回0。
1编写inttestsyscall()系统调用
编写一个系统调用意味着要给内核增加1个函数,将新函数放入文件kernel/sys.c中。新函数代码如下:
asmlingkage sys_testsyscall()
{   console_print("hello world\n");
return 0;
}

2连接新的系统调用
编写了新的系统调用过程后,下一项任务是使内核的其余部分知道这一程序的存在,然后重建包含新的系统调用的内核。
为了把新的函数连接到已有的内核中去, 需要编辑2个文件:

1).inculde/asm/unistd.h在这个文件中加入
#define_NR_testsyscall 191

2).are/i386/kernel/entry.s这个文件用来对指针数组初始化,在这个文件中增加一行:
.long SYMBOL_NAME(_sys_tsetsycall)
将.rept NR_syscalls-190改为NR_SYSCALLS-191,然后重新奖励和运行新内核。

3).使用新的系统调用
在保证的C语言库中没有新的系统调用的程序段,必须自己建立其代码如下
#inculde<linux/unistd.h>
_syscall0(int,testsyscall)
main()
{  
   tsetsyscall();
}
      在这里使用了_syscall0()宏指令,宏指令本身在程序中将扩展成名为syscall()的函数,它在main()函数内部加以调用。 在testsyscall()函数中, 预处理程序产生所有必要的机器指令代码,包括用系统调用参数值加载相应的cpu寄存器, 然后执行int 0x80中断指令。 



3.利用内核模块添加系统调用     
   模块是内核的一部分,但是并没有被编译到内核里面去。它们被分别编译并连接成一组目标文件, 这些文件能被插入到正在运行的内核,或者从正在运行的内核中移走。内核模块至少必须有2个函数:int_module和cleanup_module。第 一个函数是在把模块插入内核时调用的; 第二个函数则在删除该模块时调用。
   由于内核模块是内核的一部分,所以能访问所有内核资源。根据对linux系统调用机制的分析, 如果要增加系统调用,可以编写自己的函数来实现,然后在sys_call_table表中增加一项,使该项中的指针指向自己编写的函数, 就可以实现系统调用。下面用该方法实现在控制终端上打印“hello world” 的系统调用testsyscall()。

1)编写系统调用内核模块
#inculde(linux/kernel.h)
#inculde(linux/module.h)
#inculde(linux/modversions.h)
#inculde(linux/sched.h)
#inculde(asm/uaccess.h)
#define_NR_testsyscall 191
extern viod *sys_call+table[];
asmlinkage int testsyscall()
{ printf("hello world\n");
return 0;
}
int init_module()
{ sys_call_table[_NR_tsetsyscall]=testsyscall;
printf("system call testsyscall() loaded success\n");
    return 0;
    }
void cleanup_module()
{

}

2)使用新的系统调用#define<linux/unistd.h>
#define_NR_testsyscall 191
_syscall0(int,testsyscall)
main()

    testsyscall();
}

3)编译内核模块并插入内核
       编译内核的命令为:gcc -Wall -02 -DMODULE -D_KERNEL_-C syscall.c
   -Wall通知编译程序显示警告信息;参数-02 是关于代码优化的设置, 内核模块必须优化; 参数-D_LERNEL通知头文件向内核模块提供正确的定义; 参数-D_KERNEL_通知头文件, 这个程序代码将在内核模式下运行。编译成功后将生成 syscall.0文件。最后使用insmod syscall.o命令将模块插入内核后即可使用增加的系统调用。

    比较以上二种方法,笔者认为采用内核模块的方法较好。因为这种方法可省去编译新内核并用新内核重新 启动的麻烦,这一优点对于代码的调试是非常有价值的, 可以节省大量时间。
===========================
[from]http://hi.baidu.com/pizzapark/blog/item/0dd4b68fb118f4ebf01f3638.html

虚拟一个简单的字符设备驱动,在Red Hat2.4.20-8下通过:(该程序采用转自网络的代码修改而成,详细情况请查看http://bbs.whnet.edu.cn/main.html :Linux版块 ----->精华区-----> 编程解疑-----> linux课设总结之字符设备驱动)

1,编写源文件:

#define  __NO_VERSION__ 
#include<linux/version.h> /*以下是本程序包含的头文件*/
#include<linux/module.h>
#include<linux/config.h>
#include<asm/uaccess.h>
#include<linux/types.h>
#include<linux/fs.h> 
#include<linux/mm.h>
#include<linux/errno.h>
#include<asm/segment.h>
#define BUFSIZE 256  /*设备中包含的最大字符数*/

char * temp;  /*该指针用于为这个虚拟的设备分配内存空间*/
unsigned int major = 0;
static ssize_t device_read(struct file * file,char * buf,size_t count,loff_t * f_pos)
{
 int i;
 if(count>BUFSIZE){  /*如果要求读出的数目多于设备固有的数目则提示并返回*/
 printk("Can't Read , the Count is to Big !\n"); 
 return  -EFAULT;
 }
 for(i = 0;i < count;i++)  /*否则,进行读出操作*/
  {__put_user(temp[i],buf);
         buf++;
        }     
 return count;
}

static ssize_t device_write(struct file * file,const char * buf,size_t count,loff_t * f_pos)
{
 int i;
 if(count>BUFSIZE){  /*要求写入的数目比设备的容量大则提示并返回*/
 printk("Can't Write , the Count is to Big\n");  
 return  -EFAULT;
 }
 for(i = 0;i < count;i++)  /*否则,进行写入操作*/
  {__get_user(temp[i],buf);
         buf++;
        }     
 return count;
}

static int device_open(struct inode * inode,struct file * file) /*打开设备函数*/
{
 temp = (char *)kmalloc(BUFSIZE,GFP_KERNEL);  /*为设备分配内存空间*/
 MOD_INC_USE_COUNT;
 return 0;
}

static int device_release(struct inode * inode,struct file * file)
{
 kfree(temp);   /*释放设备占用内存空间*/
 MOD_DEC_USE_COUNT;
 return 0;
}

struct file_operations fops = { /*填充file_operations结构*/
 read:device_read,
        write:device_write,
        open:device_open,
        release:device_release
};
    
int init_module(void)  /*登记设备函数,insmod时调用*/
{
 int num;
 num = register_chrdev(0,"mydriver",&fops); /*系统自动返回一个未被占用的设备号*/
 if(num < 0){     /*登记未成功,提示并返回*/
  printk("Can't Got the Major Number !\n");
    return num;
 }
 if(major == 0) 
 major = num;
 return 0;
}

void cleanup_module(void)  /*释放设备函数,rmmod时调用*/
{
 unregister_chrdev(major,"mydriver");
}

命名为"mydriver.c"

2,编译文件:

gcc -O2 -Wall -DMODULE -D__KERNEL__ -DLINUX -I/usr/src/linux-2.4.20-8/include -c mydriver.c

3,装载驱动:

insmod -f mydriver.o

用:lsmod可以看到mydriver.o

用:cat /proc/mydevice查看该设备的主设备号,在我的系统上为254

4,创建设备文件:mknod /dev/myDriver c 254 0 (其中myDriver为文件名,注意和下面的测试程序中打开的文件名一致,c代表此为字符设备,254位主设备号,0为从设备号,一般为0)
5,编译测试程序:test.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(void)
{
 int fd;
 int i;
  char *buf="1234567890abcdefghij\n";
  char buf1[22]; 
 
 fd = open("/dev/myDriver",O_RDWR); /*此处myDriver和建立设备文件时的名称一致*/
 if (fd == -1)
 {
  printf("Can't open file \n");
  exit(-1);
 }    
 write(fd,buf,22);
 read(fd,buf1,22);
 for(i=0;i<22;i++)
    printf("%c",buf1[i]);
 close(fd);
 return 0;
}

编译并运行:gcc test.c -o test

                       ./test

注:以上只声明了增加驱动程序的步骤和给出了源代码 ,详细情况请见文首的链接。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值