24小时学通Linux内核之向内核添加代码

 24小时学通Linux内核之向内核添加代码html

  睡了个好觉,很晚才起,很久没有这么舒服过了,今天的任务不重,因此压力不大,呵呵,如今的天气真的好冷,不过实验室有空调,我仍是喜欢待在这里,有一种不同的感受,在写了这么多天以后,本身有些不懂的页渐渐的豁然开朗了吗,并且也交到了一些朋友,真是至关开心啊。今天将介绍一下向内核中添加代码,一块儿来看看吧~node

  先来熟悉一下文件系统,经过/dev能够访问Linux的设备,咱们以men设备驱动程序为例来看看随机数是如何产生的,源代码在dirvers/char/mem.c上能够查看linux

static int memory_open(struct inode * inode * inode,struct file * filp) { switch (iminor(inode)) {  //switch语句根据从设备号来初始化驱动程序的数据结构 case 1: ... case 8: filp->f_op = &random_fops; break; case 9: filp->f_op = &urandom_fops; break;

  那么上述程序的filps和fop是什么呢?实际上filp只是一个文件结构指针,而fop是一个file_operations结构指针,内核经过file_operations结构来肯定操做文件时要调用的函数,下面的file_operations结构用于随机设备驱动的部份内容,代码在include/linux/fs.h上能够查看到:程序员

struct file { struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; ... struct address_space *f_mapping; };

  q驱动程序所实现的函数必须符合file_operations结构中所列出的函数原型,代码在dirvers/char/random.c上能够查看:安全

struct file_operations random_fops = { .read = random_read, .write = random_write, .poll = random_poll,  //poll操做容许某种操做以前查看该操做是否阻塞 .ioctl = random_ioctl, };  //随机设备提供的操做有以上 struct file_operations urandom_fops = { .read = random_read, .write = random_write, .ioctl = random_ioctl, }; //urandom设备提供的操做有以上

  若是设备驱动程序在内核空间运行,可是缓冲区却位于用户空间,那咱们该如何才能安全访问buf中的数据呢,下面来讲下数据在用户空间和内核空间之间的奥秘,Linux提供的copy_to_user()和copy_from_user()使得驱动程序能够在内核空间和用户空间上传递数据,在read_random()中,经过extract_entropy()函数来实现这个功能,下面代码在dirvers/char/random.c上能够查看(下面的代码没有敲完,主要是否是很懂,望大神指教)数据结构

static ssize_t extract_entropy(struct entract_syore *r,void *buf,size_t nbytes,int flags) { ... { static ssize_t extract_entropy(struct entropy_store *r,void *buf,size_t nbytes,int flags) { ...

  内核空间和用户空间的程序可能都须要使用已经得到的随机数,内核空间的程序能够经过不设置标志位来避免函数copyto_user()带来的额外开销。除了经过设备驱动程序向内核添加代码以外,还有别的方式 的,用户空间能够经过系统调用来访问内核服务程序和系统硬件,这里很少阐释,都知道有这回事就好了。app

 

  下面咱们来介绍怎么去编写源代码,当咱们去编写一个复杂的设备驱动程序时,也许要输出驱动程序中定义的某些符合,以便让内核其它模块使用,这些一般被用在低级的驱动程序中,以便根据这些基本的函数来构建更高级的驱动程序,在Linux2.6内核中,code monkey能够用以下两个宏输出符号,代码在include/linux/module.h中查看:dom

#define EXPORT_SYMBOL(sym) __EXPORT_SYMBOL(sym, ""#define EXPORT_SYMBOL_GPL(sym) __EXPORT_SYMBOL(sym, "_gpl")

  目前为止,咱们介绍的设备 驱动程序都是主动操做,或者对设备的数据进行读写操做,那么它的功能不止这些的时候会怎么样呢?在Linux中,设备驱动程序解决这些问题的典型方式就是使用ioctl。ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。函数

调用个数以下:大数据

int ioctl(int fd, ind cmd, …);

其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,通常最多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性份量,就是说若是你的驱动程序提供了对ioctl的支持,用户就能够在用户程序中使用ioctl函数控制设备的I/O通道。

 

ioctl命令号:

dir:

  表明数据传输的方向,占2位,能够是_IOC_NONE(无数据传输,0U),_IOC_WRITE(向设备写数据,1U)或_IOC_READ(从设备读数据,2U)或他们的逻辑或组合,固然只有_IOC_WRITE和_IOC_READ的逻辑或才有意义。

type:

描述了ioctl命令的类型,8位。每种设备或系统均可以指定本身的一个类型号,ioctl用这个类型来表示ioctl命令所属的设备或驱动。通常用ASCII码字符来表示,如 'a'。

  nr:

ioctl命令序号,通常8位。对于一个指定的设备驱动,能够对它的ioctl命令作一个顺序编码,通常从零开始,这个编码就是ioctl命令的序号。

  size:

ioctl命令的参数大小,通常14位。ioctl命令号的这个数据成员不是强制使用的,你能够不使用它,可是咱们建议你指定这个数据成员,经过它咱们能够检查用户空间数据的大小以免错误的数据操做,也能够实现兼容旧版本的ioctl命令。

 

ioctl返回值:

   ioctl函数的返回值是一个整数类型的值,若是命令执行成功,ioctl返回零,若是出现错误,ioctl函数应该返回一个负值。这个负值会做为errno值反馈给调用此ioctl的用户空间程序。关于返回值的具体含义,请参考<linux/errno.h>和<asm/errno.h>头文件。

 

ioctl参数:

  首先要说明这个参数是有用户空间的程序传递过来的,所以这个指针指向的地址是用户空间地址,在Linux中,用户空间地址是一个虚拟地址,在内核空间是没法直接使用它的。为了解决在内核空间使用用户空间地址的数据,Linux内核提供了如下函数,它们用于在内核空间访问用户空间的数据,定义在<asm/uaccess.h>头文件中:

unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n); unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n);

copy_from_user和copy_to_user通常用于复杂的或大数据交换,对于简单的数据类型,如int或char,内核提供了简单的宏来实现这个功能:

#define get_user(x,ptr)
#define put_user(x,ptr)//x是内核空间的简单数据类型地址,ptr是用户空间地址指针。

  

cmd参数如何得出:

  一个cmd参数被分为4段,每段都有其特殊的含义,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数经过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中获得设备的类型、序列号、传送方向、数据尺寸等信息,而后经过switch{case}结构进行相应的操做。解释一下四部分,所有都在<asm-generic/ioctl.h>和ioctl-number.txt这两个文档有说明的 。

1)幻数:说得再好听的名字也只不过是个0~0xff的数,占8bit(_IOC_TYPEBITS)。这个数是用来区分不一样的驱动的,像设备号申请的时候同样,内核有一个文档给出一些推荐的或者已经被使用的幻数

2)序数:用这个数来给本身的命令编号,占8bit(_IOC_NRBITS),个人程序从1开始排序。

 

3)数据传输方向:占2bit(_IOC_DIRBITS)。若是涉及到要传参,内核要求描述一下传输的方向,传输的方向是以应用层的角度来描述的。

  • _IOC_NONE:值为0,无数据传输。
  • _IOC_READ:值为1,从设备驱动读取数据。
  • _IOC_WRITE:值为2,往设备驱动写入数据。
  • _IOC_READ|_IOC_WRITE:双向数据传输。

4)数据大小:与体系结构相关,ARM下占14bit(_IOC_SIZEBITS),若是数据是int,内核给这个赋的值就是sizeof(int)。

 

 ioctl如何实现:

  在驱动程序中实现的ioctl函数体内,其实是有一个switch{case}结构,每个case对应一个命令码,作出一些相应的操做。怎么实现这些操做,这是每个程序员本身的事情,由于设备都是特定的,这里也无法说,关键在于怎么样组织命令码,由于在ioctl中命令码是惟一联系用户程序命令和驱动程序支持的途径。

  命令码的组织是有一些讲究的,由于咱们必定要作到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会致使不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是很是困难的事情

因此在Linux核心中是这样定义一个命令码的:

____________________________________

| 设备类型 | 序列号 | 方向 |数据尺寸|

|----------|--------|------|--------|

| 8 bit    |  8 bit |2 bit |8~14 bit|

|----------|--------|------|--------|

  这样一来,一个命令就变成了一个整数形式的命令码。可是命令码很是的不直观,因此Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码获得一些用户能够理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

 

  在内核中是没法直接访问用户空间地址数据的。所以凡是从用户空间传递过来的指针数据,务必使用内核提供的函数来访问它们。这里有必要再一次强调的是,在内核模块或驱动程序的编写中,咱们强烈建议你使用内核提供的接口来生成并操做ioctl命令号,这样能够对命令号赋予特定的含义,使咱们的程序更加的健壮;另外一方面也能够提升程序的可移植性。

 

  最后咱们来介绍一下添加代码后的编译和调试,在内核中添加代码后就须要不断运行,修复错误,咱们知道当对/proc文件系统进行读写操做时,它的每个结点都连接到一个内核函数,在Linux2.6内核中,要想你的设备可以被访问,首先就要在/proc文件系统中建立一个入口,这个能够经过creat_proc_read_entry()来实现,代码在include/linux/proc_fs.h上查看:

static inline struct proc_dir_entry *create_proc_read_entry(const char *name,
    mode_t mode,struct proc_dir_entry *base,
    read_proc_t *read_proc,void * data)

*name是结点在/proc文件系统的入口,*base指向设置proc文件的目标路径,若是它的值为NULL,表示该文件就在/proc目录下,读取该文件能够调用*read_proc指向的函数。这里也很少加阐释了,整个也是很简单的过程。

  小结

  今天的重点是iotcl函数了,其中还有不少向内核中添加代码的细节没有讲到,主要是这些都涉及到过多的操做,须要你们多看源代码而且多动手在Linux上操做才能彻底掌握,,今天写的一些也借鉴了一些大牛的文章,总之 收获不少,最后几天了,真的是很开心啦,和你们一块儿分享真的很快乐的~~

 

  版权全部,转载请注明转载地址:http://www.cnblogs.com/lihuidashen/p/4255826.html

相关文章
相关标签/搜索