Chinaunix首页 | 论坛 | 博客
  • 博客访问: 6320043
  • 博文数量: 2759
  • 博客积分: 1021
  • 博客等级: 中士
  • 技术积分: 4091
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-11 14:14
文章分类

全部博文(2759)

文章存档

2019年(1)

2017年(84)

2016年(196)

2015年(204)

2014年(636)

2013年(1176)

2012年(463)

分类: LINUX

2013-08-24 09:32:44

一、设备ioctl控制

1>功能:大部分驱动出了需要具备读写设备的能力外,还需要具备对硬件控制的能力。例如,要求设

备报告错误信息,改变波特率,这些操作常常通过ioctl方法来实现。

2>用户空间使用方法:

    在用户空间,使用ioctl系统调用来控制设备,原型如下

int ioctl(int fd, unsigned long cmd,...)

    原型中的点表示这是一个可选的参数,存在与否依赖于控制命令(第2个参数)是否涉及到与设

备的数据交互。

3>驱动ioctl的方法

iotcl驱动方法有和用户空间版本不同的原型:
    int(*ioctl)(struct inode* inode, struct file* filp,unsigned int cmd, unsigned long arg)(从2.6.36版本开始已废弃)

long(*inlocked_ioctl)(struct file* filp, unsigned int cmd, unsogned long arg);

cmd参数从用户空间传下来,可选的参数arg以一个unsigned long的形式传递,不管它是一个整数或

一个指针。如果cmd命令不涉及数据传输,则第三个参数arg的值五任何意义。

3>ioctl实现

    1、定义命令
    字编写unlock_ioctl代码之前,首先需要定义命令。为了防止对错误的设备使用正确的命令,命

令号应该在系统范围内是唯一的。ioctl命令编码被规划为几个位段,include/asm/ioctl.h中定义了

这些字段:类型(幻数), 序号,传递方向,参数的大小。Documentation/ioctl-number.txt文件

中罗列了在内核中已经使用了的幻数。

内核提供了下列宏来帮助定义命令:

_IO(type,nr): 没有参数的命令

_IOR(type,nr,datatype):从驱动中读数据

_IOW(type,nr datatype):从驱动中写数据

_IOWR(type,nr datatype):双向传送,type和number成员作为参数被传递。

定义命令范例:

#define MEM_IOC_MAGIC 'm'//定义幻数

#define MEM_IOCSET
_IOW(MEM_IOC_MAGIC,0,int)

#define MEM_IOCGSET
_IOR(MEM_IOC_MAGIC,1,int)

    2、实现命令

    定义好了命令,下一步就是实现unlock_ioctl函数了,unlock_inctl函数的实现包括如下3个技术环节:

1、返回值
    unlock_ioctl函数的实现通常是根据命令执行的一个switch语句,但是,当命令号不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(“非法参数”)。


2、参数使用
    如何使用unlock_ioctl中的参数arg?如果是一个整数,可以直接使用。如果是指针,我们必须确保这个用户地址是有效的,因此使用前需要进行正确的检查。

参数检测:

不需要检测:copy_from_user,copy_to_user,get_user,put_user
需要检测:__get_user,__put_user

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

第一个参数是VERIFY_READ或者VERIFY_WRITE,用来表明是读用户内存还是写用户内存。addr参数是

要操作的用户内存地址,size是操作的长度。如果ioctl粗腰从用户空间度一个整数,那么size参数

等于sizeof(int).access_ok返回一个布尔值:1是成功(存取没问题)和0是失败(存取有问题),

如果该函数返回失败,则ioctl应当返回-EFAULT.
/*参数检测*/
if(_IOC_DIR(cmd)&_IOC_READ)
    err = !access_ok(VERIFY_WRITE,(void__user*)arg,_IOC_SIZE(cmd));
    
else if(_IOC_DIR(cmd)&_IOC_WRITE)
    err = !access_ok(VERIFY_READ,(void__user*)arg,_IOC_SIZE(cmd));

if(err)
    return -EFAULT;

3、命令操作
/*命令操作*/
switch(cmd)
{
    case MEM_IOCSQUANTUM:/*set:arg points to the value*/
    retval = __get_user(scull_quantum,(int*)arg);
    break;

    case MEM_IOCGQUANTUM:/*Get:arg is pointer to result*/
    retval = __put_user(scull_quantum,(int*)arg);
    break;

    default:
    return -EINVAL;
}

代码范例:


点击(此处)折叠或打开

  1. /**************memdev.h*******************/

  2. #ifndef _MEMDEV_H_
  3. #define _MEMDEV_H_

  4. #include <linux/ioctl.h>

  5. #ifndef MEMDEV_MAJOR
  6. #define MEMDEV_MAJOR 0 /*预设的mem的主设备号*/
  7. #endif

  8. #ifndef MEMDEV_NR_DEVS
  9. #define MEMDEV_NR_DEVS 2 /*设备数*/
  10. #endif

  11. #ifndef MEMDEV_SIZE
  12. #define MEMDEV_SIZE 4096
  13. #endif

  14. /*mem设备描述结构体*/
  15. struct mem_dev
  16. {
  17.     char *data;
  18.     unsigned long size;
  19. };

  20. /* 定义幻数 */
  21. #define MEMDEV_IOC_MAGIC 'k'

  22. /* 定义命令 */
  23. #define MEMDEV_IOCPRINT _IO(MEMDEV_IOC_MAGIC, 1)
  24. #define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
  25. #define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)

  26. #define MEMDEV_IOC_MAXNR 3

  27. #endif /* _MEMDEV_H_ */


  28. /**************memdev.c*******************/

  29. #include <linux/module.h>
  30. #include <linux/types.h>
  31. #include <linux/fs.h>
  32. #include <linux/errno.h>
  33. #include <linux/mm.h>
  34. #include <linux/sched.h>
  35. #include <linux/init.h>
  36. #include <linux/cdev.h>
  37. #include <linux/slab.h>
  38. #include <asm/io.h>
  39. #include <asm/system.h>
  40. #include <asm/uaccess.h>

  41. #include "memdev.h"

  42. static int mem_major = MEMDEV_MAJOR;
  43. module_param(mem_major, int, S_IRUGO);

  44. struct mem_dev *mem_devp; /*设备结构体指针*/
  45. struct cdev cdev;

  46. /*文件打开函数*/
  47. int mem_open(struct inode *inode, struct file *filp)
  48. {
  49.     struct mem_dev *dev;
  50.     /*获取次设备号*/
  51.     int num = MINOR(inode->i_rdev);
  52.     if (num >= MEMDEV_NR_DEVS)
  53.         return -ENODEV;
  54.     dev = &mem_devp[num];
  55.     /*将设备描述结构指针赋值给文件私有数据指针*/
  56.     filp->private_data = dev;

  57.     return 0;
  58. }

  59. /*文件释放函数*/
  60. int mem_release(struct inode *inode, struct file *filp)
  61. {
  62.     return 0;
  63. }

  64. /*
  65.  * IO操作
  66.  * */
  67. static long memdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
  68. {
  69.     int err = 0;
  70.     int ret = 0;
  71.     int ioarg = 0;

  72.     /* 检测命令的有效性 */
  73.     if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)
  74.         return -EINVAL;
  75.     if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)
  76.         return -EINVAL;

  77.     /* 根据命令类型,检测参数空间是否可以访问 */
  78.     if (_IOC_DIR(cmd) & _IOC_READ)
  79.         err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
  80.     else if (_IOC_DIR(cmd) & _IOC_WRITE)
  81.         err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
  82.     if (err)
  83.         return -EFAULT;

  84.     /* 根据命令,执行相应的操作 */
  85.     switch(cmd) {
  86.         /* 打印当前设备信息 */
  87.         case MEMDEV_IOCPRINT:
  88.             printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
  89.             break;
  90.         /* 获取参数 */
  91.         case MEMDEV_IOCGETDATA:
  92.             ioarg = filp->f_pos;
  93.             ret = __put_user(ioarg, (int *)arg);
  94.             break;
  95.         /* 设置参数 */
  96.         case MEMDEV_IOCSETDATA:
  97.             ret = __get_user(ioarg, (int *)arg);
  98.             if ((ioarg<0) || (ioarg>MEMDEV_SIZE))
  99.                 return -EINVAL;
  100.             filp->f_pos = ioarg;
  101.             printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
  102.             break;
  103.         default:
  104.             return -EINVAL;
  105.     }
  106.     return ret;
  107. }

  108. /*
  109.  * 读函数
  110.  * */
  111. static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
  112. {
  113.     unsigned long p = *ppos;
  114.     unsigned int count = size;
  115.     int ret = 0;
  116.     struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

  117.     /*判断读位置是否有效*/
  118.     if (p >= MEMDEV_SIZE)
  119.         return 0;
  120.     if (count > MEMDEV_SIZE - p)
  121.         count = MEMDEV_SIZE - p;

  122.     /*读数据到用户空间*/
  123.     if (copy_to_user(buf, (void*)(dev->data + p), count)) {
  124.         ret = - EFAULT;
  125.     } else {
  126.         *ppos += count;
  127.         ret = count;
  128.         printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p);
  129.     }
  130.     return ret;
  131. }

  132. /*
  133.  * 写函数
  134.  */
  135. static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
  136. {
  137.     unsigned long p = *ppos;
  138.     unsigned int count = size;
  139.     int ret = 0;
  140.     struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/

  141.     /*分析和获取有效的写长度*/
  142.     if (p >= MEMDEV_SIZE)
  143.         return 0;
  144.     if (count > MEMDEV_SIZE - p)
  145.         count = MEMDEV_SIZE - p;

  146.     /*从用户空间写入数据*/
  147.     if (copy_from_user(dev->data + p, buf, count))
  148.         ret = - EFAULT;
  149.     else {
  150.         *ppos += count;
  151.         ret = count;
  152.     }
  153.     return ret;
  154. }

  155. /*文件操作结构体*/
  156. static const struct file_operations mem_fops =
  157. {
  158.     .owner = THIS_MODULE,
  159.     .open = mem_open,
  160.     .release = mem_release,
  161.     .unlocked_ioctl = memdev_ioctl,
  162.     .read = mem_read,
  163.     .write = mem_write,
  164. };

  165. /*
  166.  * 设备驱动模块加载函数
  167.  * */
  168. static int memdev_init(void)
  169. {
  170.     int result;
  171.     int i;

  172.     dev_t devno = MKDEV(mem_major, 0);
  173.     /* 静态申请设备号*/
  174.     if (mem_major)
  175.         result = register_chrdev_region(devno, 2, "memdev");
  176.     else { /* 动态分配设备号 */
  177.         result = alloc_chrdev_region(&devno, 0, 2, "memdev");
  178.         mem_major = MAJOR(devno);
  179.     }
  180.     if (result < 0)
  181.         return result;

  182.     /*初始化cdev结构*/
  183.     cdev_init(&cdev, &mem_fops);
  184.     cdev.owner = THIS_MODULE;
  185.     cdev.ops = &mem_fops;
  186.     /* 注册字符设备 */
  187.     cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);

  188.     /* 为设备描述结构分配内存*/
  189.     mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
  190.     if (!mem_devp) {/*申请失败*/
  191.         result = - ENOMEM;
  192.         goto fail_malloc;
  193.     }
  194.     memset(mem_devp, 0, sizeof(struct mem_dev));

  195.     /*为设备分配内存*/
  196.     for (i=0; i < MEMDEV_NR_DEVS; i++) {
  197.         mem_devp[i].size = MEMDEV_SIZE;
  198.         mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
  199.         memset(mem_devp[i].data, 0, MEMDEV_SIZE);
  200.     }

  201.     return 0;
  202. fail_malloc:
  203.     unregister_chrdev_region(devno, 1);

  204.     return result;
  205. }

  206. /*
  207.  * 模块卸载函数
  208.  * */
  209. static void memdev_exit(void)
  210. {
  211.     cdev_del(&cdev); /*注销设备*/
  212.     kfree(mem_devp); /*释放设备结构体内存*/
  213.     unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
  214. }

  215. MODULE_AUTHOR("David Xie");
  216. MODULE_LICENSE("GPL");

  217. module_init(memdev_init);
  218. module_exit(memdev_exit);



  219. /**************app_ioctl.c*******************/

  220. #include <stdio.h>
  221. #include <string.h>
  222. #include <stdlib.h>
  223. #include <unistd.h>
  224. #include<sys/types.h>
  225. #include<sys/stat.h>
  226. #include<fcntl.h>
  227. #include "memdev.h" /* 包含命令定义 */

  228. /* 调用命令MEMDEV_IOCSETDATA */
  229. static inline void setpos(int fd, int pos) {
  230. printf("ioctl: Call MEMDEV_IOCSETDATA to set position\n");
  231. ioctl(fd, MEMDEV_IOCSETDATA, &pos);
  232. }

  233. /* 调用命令MEMDEV_IOCGETDATA */
  234. static inline int getpos(int fd) {
  235.     int pos;
  236. printf("ioctl: Call MEMDEV_IOCGETDATA to get position\n");
  237. ioctl(fd, MEMDEV_IOCGETDATA, &pos);
  238.     return pos;
  239. }

  240. int main(void)
  241. {
  242. int fd = 0;
  243. int arg = 0;
  244. char buf[256];
  245. /*打开设备文件*/
  246. if (-1==(fd=open("/dev/memdev0",O_RDWR))) {
  247. printf("Open Dev Mem0 Error!\n");
  248. _exit(EXIT_FAILURE);
  249. }
  250. /* 调用命令MEMDEV_IOCPRINT */
  251. printf("ioctl: Call MEMDEV_IOCPRINT to printk in driver\n");
  252.     ioctl(fd, MEMDEV_IOCPRINT, &arg);
  253.    
  254.     strcpy(buf, "This is a test for ioctl");
  255.     printf("write %d bytes in new open file\n", strlen(buf));
  256.     write(fd, buf, strlen(buf));
  257.     printf("new pos is %d\n", getpos(fd));
  258.     
  259.     setpos(fd, 0);
  260.     printf("set pos = 0\n");
  261.     printf("after setpos(fd, 0), new pos is %d\n", getpos(fd));
  262.      
  263. close(fd);
  264. return 0;
  265. }



二、内核等待队列

在Linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列可看做保存进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待队里中取出进程

1>操作等待队里

1、定义等待队列

wait_queue_head_t my_queue

2、初始化等待队列

init_waitqueue_head(&my_queue)

3、定义并初始化等待队列

DECAARE_WAIT_QUEUE_HEAD(my_queue)


4、有条件睡眠

wait_event(queue,condition)

    当condition(一个布尔表达式)为真时,立即返回;否则进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上。

wait_event_interruptible(queue,condition)

    当condition(一个布尔表达式)为真时,立即返回;否则进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。

int wait_event_killable(wait_queue_t queue,condition)

    当condition为真时,立即返回;否则进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。

5、无条件睡眠(建议不再使用)

sleep_on(wait_queue_head_t* q)

    让进程进入不可中断的睡眠,并把它放入等待队列q。

interruptible_sleep_on(wait_queue_head_t* q)

    让进程进入可中断的睡眠,并把它放入等待队列q。

6、从等待队列中唤醒进程

wake_up(wait_queue* q)

    从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE的所有进程

wake_up_interruptible(wait_queue_t* q)

    从等待队列q中唤醒状态为TASK_INTERRUPTIBLE的进程。


三、阻塞型字符设备驱动

1>当一个设备无法立刻满足用户的读写请求时应当如何处理?

例如:调用read时没有数据可读,但以后可能会有;或者一个进程试图向设备写入数据,但设备暂时

没有准备好接受数据。应用程序通常不关心这种问题,应用程序只是调用read或write并得到返回

值。驱动程序应当(缺省地)阻塞进程,使得它进入睡眠,知道请求可以得到满足。

2>阻塞方式

在阻塞型驱动程序中,read实现方式如下:如果进程调用read,但设备没有数据或数据不足,进程阻

塞。当新的数据到达后,唤醒被阻塞进程。write的实现方式如下:如果进程调用了write,但设备

有足够的空间供其写入数据,进程阻塞。当设备中的数据被读走后,缓冲区中空出部分空间,则换醒

进程。

3>非阻塞方式

阻塞方式是文件读写操作的默认方式,但应用程序可通过使用O_NONBLOCK标志来人为的设置读写操作

非阻塞方式(该标志定义中,在打开文件时指定)。如果设置了O_NONBLOCK标

志,read和write的行为是不同的。如果进程在没有数据就绪时调用了read,或者在缓冲区没有空间

时调用了write,系统只是简单地返回-EAGIN,而不会阻塞进程。


四、Poll设备操作

1>select系统调用

select系统调用用于多路监控,当没有一个文件满足要求时,select将阻塞调用进程。

int select(int maxfd,fd_set* readfds,fd_set* writefds,fe_set* execeptfds,const struct timeval* timeout)

maxfd:文件描述符的范围,比待检测的最大文件描述符大1.

readfds:被读监控的文件描述符集

writefds:被写监控的文件描述符集

exceptfds:被异常监控的文件描述符集

timeout:定时器

timeout取不同的值,该调用由不同的表现:

timeout为0,不管是否有文件满足要求,都立刻返回,无文件满足要求返回0,有文件满足要求返回一个正值。

timeout为NULLselect将阻塞进程,知道某个文件满足要求

timeout值为正整数,就是等待的最长时间,即select在timeout时间内阻塞进程

select调用返回时,返回值有如下情况:

1、正常情况下返回满足要求的文件描述符个数

2、经过了timeout等待后仍无文件满足要求,返回值为0

3、如果select被某个信号中断,它将返回-1设置errno为EINTR

4、如果出错,返回-1并设置相应的errno

使用方法:

1、将要监控的文件添加到文件描述符集;

2、调用select开始监控

3、判断文件是否发生变化

系统提供了4个宏对描述符集进行操作:

#include
void FD_SET(int fd,fd_set* fdset)
void FD_CLR(int fd,fd_set* fdset)
void FD_ZERO(int fd,fd_set* fdset)
void FD_ISSET(int fd,fd_set* fdset)

FD_SET将文件描述符fd添加到文件描述符集fdset中;
FD_CLR从文件描述符集fdset中清除文件描述符fd;
FD_ZERO清空文件描述符集fdset;

在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件fd发生了变化。

2>Poll方法

应用程序使用select系统调用,它可能阻塞进程。这个调用由驱动的poll方法实现,原型为:

unsigned int(*poll)(struct file* filp,poll_table* wait)

poll设备方法负责完成:

1、使用poll_wait将等待队列添加到poll_table中。

2、返回描述设备是否可读或可写的掩码


掩码:
POLLIN:设备可读
POLLRDNORM:数据可读
POLLOUT:设备可写
POLLWRNORM:数据可写

设备可读通常返回(POLLIN|POLLRDNORM
设备可写通常返回(POLLOUT|POLLWRNORM

static unsigned int mem_poll(struct file* filp,poll_table* wait)
{
    struct scull_pipe* dev = filp->private_data;
    unsigned int mask = 0;

    /*把 等待队列 添加到poll_table*/
    poll_wait(filp,&dev->inq,wait);

    /*返回掩码*/
    if(有数据可读)
        mask = POLLIN|POLLRDNORM;/*设备可读*/
    
    return mask;    

}
Poll方法只是做一个登记,真正的阻塞发生在select.c中的do_select函数。


五、自动创建设备文件

自动创建(2.4内核)

devfs_register(devfs_handle_t dir, const char* name, unsigned int flags, unsigned int major, unsigned int minor, umode_t mode, void* ops, void* info)

在指定的目录张创建设备文件。dir:目录名,为空表示在/dev/目录下创建;name:文件名;flags:创建标志;major:主设备号; minor:次设备号;mode:创建模式;ops:操作函数集;info:通常为空

自动创建(2.4内核)

从Linux2.6.13开始,devs不复存在了,udev成为devfs的替代。相比devfs,udev(mdev)存在与应

用层,利用udev(mdev)来说实现设备文件的自动创建很简单,在驱动初始化的代码里调用

class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。

例子:
struct class* myclass = class_create(THIS_MODULE,"my_device_driver");

device_create(myclass,NULL,MKDEV(maior_num,0),NULL,"my_device");

当驱动加载时,udev(mdev)就会自动在/dev下创建my_device设备。
阅读(741) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~