Chinaunix首页 | 论坛 | 博客
  • 博客访问: 180383
  • 博文数量: 32
  • 博客积分: 499
  • 博客等级: 下士
  • 技术积分: 347
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-22 14:47
文章存档

2012年(10)

2011年(19)

2010年(3)

分类: LINUX

2011-02-19 18:54:05

    之所以称之为高级字符驱动程序操作,是lddr3的作者相对于第三章而言的。若想把功能搞强大,必然就得学更多的。
    ioctl是一个牛逼的东东,之所以牛逼,在于所谓的随心所欲,导致了不是很好啊掌握。首先说什么是ioctl呢,in/out control,输入输出控制,从英语的字面上还是比较好理解。用比拟的方法来讲解或许更好:你是一个美丽的姑娘,然后呢,我就想追你,怎么办呢,鲜花,邂逅,要号等方法。这里,有个对象,就是你,我们想采取方式,比如鲜花,邂逅,要号等,而这些方式都是你自己选择的。而对于每一种方法,有个参数,比如鲜花的数量,邂逅的有个地点,要号可以选择qq,手机号等。
简言之就是:对某个对象采取(参数)措施。其实这就是ioctl。下面我们引出它的原型。
用户空间为:
int ioctl(int fd,unsigned long cmd,...);
驱动空间中为:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
虽然看起来形式不一样,但是本质是一样的,innode和filp显然和fd对应,arg与...对应。
讲用户空间的即可,fd为文件,即对象,正如上面的姑娘,然后cmd就是命令,这是我们选择的,正如上面采取追女孩的方法,参数就不解释了。对象和参数都没有问题,唯一我们不清楚的就是cmd这个命令了。下面具体讲解下。
正如电影里面的一样,每一个命令就如一个命令执行者,他的有个ID,4个字段。
1.type,叫幻数(8位),其实就理解为主设备号就好了,多用一个字母表示。
2.number,序数(8位),理解为次设备号
3.direction,数据传输方向,注意:你应该从应用程序的角度去看这个方向。先列出来。
_IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (数据在2个方向被传送).
你把你的眼睛放在应用程序上,懂吧,这样说吧,你在家里,叫你买东西,你就得出去买,叫你卖庄稼,你就把家你的庄稼拿出去卖。就是这样的,你家住在应用程序,_IOC_READ就是叫你到驱动程序中读取数据,懂了吧。_IOC_WRITE当然就把自己家应用程序的写到驱动程序中。
4.size,数据大小(13或者14位)
 
    实践中编号多半都是用一些构造编号的宏来搞定的_IOC_WRITE,包含在 中,如_IO(type,nr)(给没有参数的命令), _IOR(type, nre, datatype)(给从驱动中读数据的), _IO(type,nr,datatype)(给写数据), 和 _IOWR(type,nr,datatype)(给双向传送).
    编号我们也是不可能记得住的,我们可以用宏的方式让其值给有意义的方法,就如你叫13号球员,我必须和记得数字,但是你给它取个名字叫张三,用的是很不是叫13而是叫张三就可以了。此处的张三其实也就是上面说的cmd,但是,只是名字和编号对应,具体的功能我们还得在内核空间中自己去勾画。具体如何做到的呢,下面我们来具体简单实例来说明。
   比如我的需求是给quantum赋值为10,这样我们需要写个赋值命令
SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,1,int),我们怎么知道赋值成功没有呢,于是再写个读quantum的命令SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC,2),应用空间中,我们就写为如下的scull_test.c
 
  1. #include <unistd.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <stdlib.h>
  5. #include <linux/ioctl.h>

  6. #define SCULL_IOC_MAGIC 'k'
  7. #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
  8. #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)


  9. int main()
  10. {
  11.     int sculltest;
  12.     int code;
  13.     
  14.     if ((sculltest = open("/dev/scull",O_RDWR )) < 0)    {
  15.          printf("open error! \n");
  16.         exit(1);
  17.     }

  18.      printf("open scull ! \n");
  19.     code = 10;
  20.     if ( ioctl( sculltest , SCULL_IOCSQUANTUM , &code ) < 0)     {
  21.          printf("ioctl SCULL_IOCSQUANTUM error! \n");
  22.         exit(1);
  23.     }

  24.     printf("scull_quantum=%d \n" , ioctl( sculltest , SCULL_IOCQQUANTUM , NULL ) );    
  25.     close(sculltest);
  26.         printf("close scull ! \n");

  27.     printf("\n");
  28.   exit(0);
  29. }
上面的已经很好理解了,先利用工具构造好命令的ID,然后在下面的主函数中运用。这里是写好了,这毕竟是写驱动,于是在驱动中我们得写个ioctl的命令处理函数,正如写write和read函数一样,我们在写驱动的scull.c中加入如下函数scull_ioctl()
  1. int scull_ioctl(struct inode *inode, struct file *filp,
  2.                  unsigned int cmd, unsigned long arg)
  3. {

  4.     int err = 0, tmp;
  5.     int retval = 0;
  6.     if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
  7.     if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
  8.     if (_IOC_DIR(cmd) & _IOC_READ)
  9.         err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
  10.     else if (_IOC_DIR(cmd) & _IOC_WRITE)
  11.         err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
  12.     if (err) return -EFAULT;

  13.     switch(cmd) {
  14.      case SCULL_IOCSQUANTUM:         if (! capable (CAP_SYS_ADMIN))
  15.             return -EPERM;
  16.         retval = __get_user(scull_quantum, (int __user *)arg);
  17.         break;
  18.      case SCULL_IOCQQUANTUM:         return scull_quantum;
  19.      default:
  20.         return -ENOTTY;
  21.     }
  22.     return retval;

  23. }

暂时只讲下面的switch(cmd),当你带着你的ID来指认自己的孩子的时候,switch让你顺路找到孩子,就是这么简单的。

ioctl基本就讲完了,整体的样子就描述出来了(难点是要把用户空间于驱动内核空间区别开来)。还有一些细节问题,下面再慢慢讲。

1.switch的返回值,这个简单,书上也写了,自己看。

2.ioctl参数验证,参数进入内核就如你进大门,你如果是整数,就代表你是住这里的,随便近,如果你是指针,那就代表你是外来者,勇猛用access_ok来拷问(中声明)。

int access_ok(int type, const void *addr, unsigned long size);

type只能为VERIFY_READ 或者 VERIFY_WRITE, 依据这个要进行的动作是否是读用户空间内存区或者写它。addr和size较好理解。举个例子就是上面代码中的

access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));

3.权能和受限操作。

给你了权限你才可以做权限内的事,这个大家都懂的,我们用captable函数实现(定义在

if (! capable (CAP_SYS_ADMIN))
   return -EPERM;

这段代码取之上面那一大段中,就是看你有CAP_SYS_ADMIN(还有其他的,书上都写好了的)这样的权限没有,没有当然就悲剧去谈后世。

最后加上代码,是别人写好的的比较完善的,我就不用自己写了。

 ioctl_and_llseek.rar  

这个别人写好的代码中,驱动的makefile多半是不能用的,因为作者是采用他的环境写的,下面添加段makefile的代码,然更多的情况和环境可以用。

  1. ifneq ($(KERNELRELEASE),)

  2. obj-m:=scull.o

  3. else

  4. KERNELDIR:=/lib/modules/$(shell uname -r)/build

  5. PWD:=$(shell pwd)

  6. default:

  7.     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

  8. clean:

  9.     rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.order

  10. endif
阅读(2181) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~