国嵌视频学习——高级字符设备驱动

国嵌视频学习——高级字符设备驱动

https://blog.csdn.net/li4850729/article/details/7574913


Ioctl设备控制

大部分驱动除了需要具备读写设备的能力外,还需要具备对硬件控制的能力。例如,要求设备报告错误信息,改变波特率,这些操作常常通过ioctl方法来完成

用户使用方法

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

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

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

返回值为假则操作失败

驱动ioctl方法

ioctl驱动方法有和用户空间版本不同的原型:

int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)

cmd参数从用户空间传下来,可选的参数arg以一个unsigned long的形式传递,不管它是一个整数或一个指针。如果cmd命令不涉及数据传输,则第3个参数arg的值无任何意义

ioctl实现

如何实现ioctl方法?
——步骤:1.定义命令;2.实现命令

定义命令(32位)

在编写ioctl代码之前,首先需要定义命令。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。Ioctl命令编码被划分为几个位段,include/asm/ioctl.h中定义了这些位字段:类型(幻数)、序号、传送方向、参数的大小。Documentation/ioctl-number.txt文件中罗列了在内核中已经使用了的幻数

定义ioctl命令的正确方法是使用4个位段,这个列表中介绍的符号定义在<linux/ioctl.h>中:

——type:幻数(类型):表明哪个设备的命令,在参考了ioctl-number.txt之后选出,8位宽    _IO_TYPE(cmd) != ...

——number:序号,表明设备命令中的第几个,8位宽           _IO_NR(cmd) != .....

——direction:数据传送的方向,可能的值是_IOC_NONE(没有数据传输),_IOC_READ_IOC_WRITE。数据传送是从应用程序的观点来看的,_IOC_READ意思是从设备读(_IO_DIR(cmd) != ...)

——size:用户数据的大小。(13/14位宽,视处理器而定)      _IO_SIZE(cmd)

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

——_IO(type, nr):没有参数传递的命令。(那么direction的值为_IOC_NONEsize的值为0

——_IOR(type, nr, datatype):从驱动中读数据(4个值已经确定

——_IOW(type, nr, datatype):写数据到驱动

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

定义命令(范例)

#define MEM_IOC_MAGIC  'm' //定义幻数,一个字母刚好是8


#define MEM_IOCSET

_IOW(MEM_IOC_MAGIC, 0, int)

#define MEM_IOCGQSET

_IOR(MEM_IOC_MAGIC, 1, int)

Ioctl函数实现

定义好了命令,下一步就是要实现ioctl函数,loctl函数的实现包括:

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

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

不需要检测:

—— copy_from_user

—— copy_to_user

—— get_user

—— put_user

需要检测:

—— __get_uesr

—— __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) //将方向位段提取出来看是不是read

 err = !access_ok(VERIFY_WRITE, (void __user*)arg,

_IOC_SIZE(cmd));

//为什么_IOC_READ对应VERIFY_WRITE??

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;

}

例.

内核等待队列

等待队列

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

linux2.6内核提供了如下关于等待队列的操作:
1.定义等待队列

wait_queue_head_t  my_queue

2.初始化等待队列

init_waitqueue_head(&my_queue)

3.定义并初始化等待队列(直接代替第12两个操作)
DECLARE_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参数所指定的等待队列上(TASK_UNINTERRUPTIBLE在中断和给它发送信号的时候都不会被唤醒;TASK_KILLABLE也如此,不过在发送kill信号的时候能被唤醒)

5.无条件睡眠(老版本,不建议使用)

——sleep_on(wait_queue_head_t *q)

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

——interruptible_sleep_on(wait_queue_head_t *q)

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

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

——wake_up(wait_queue _t *q)

从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLETASK_INTERRUPTIBLETASK_KILLABLE所有进程(唤醒:将进程从睡眠状态改为TASK_RUNNING状态,不过只是就绪状态而非执行态)

——wake_up_interruptible(wait_queue_t *q)

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

阻塞型字符设备驱动

功能

前一节我们在设计简单字符驱动程序时,跳过了一个重要的问题:当一个设备无法立刻满足用户的读写请求时应当如何处理?例如:调用read时没有数据可读,但以后可能会有;或者一个进程试图向设备写入数据,但是设备暂时没有准备好接收数据。应用程序通常不关心这种问题,应用程序只是调用readwrite并得到返回值。驱动程序应当(缺省的)阻塞进程,使它进入睡眠,直到请求可以得到满足

阻塞方式

在阻塞型驱动程序中,read实现方式如下:如果进程调用read,但设备没有数据或数据不足,进程阻塞。当新数据到达后,唤醒被阻塞进程

非阻塞方式

阻塞方式是文件读写操作的默认方式,但应用程序员可通过使用O_NONBLOCK标志来人为的设置读写操作为非阻塞方式(该标志定义在<linux/fcntl.h>中,在打开文件时指定)

如果设置了O_NONBLOCK标志,readwrite的行为是不同的。如果进程在没有数据就绪时调用了read,或者在缓冲区没有空间时调用了write,系统只是简单地返回-EAGAIN,而不会阻塞进程

例:阻塞型字符驱动

POLL设备方法

什么是poll方法,功能是什么?

Select系统调用(功能)

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

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

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

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

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

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

——timeout:定时器。

Timeout取不同的值,该调用有不同的表现:

——*timeout0,不管是否有文件满足要求,都立刻返回,无文件满足要求返回0,有文件满足要求返回一个正值

——timeoutNULLselect将阻塞进程,直到某个文件满足要求

——*timeout为正整数,就是等待的最长时间,即selecttimeout时间内阻塞进程,过后进程被唤醒

返回值

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

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

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

3.如果select被某个信号中断,它将返回-1并设置errnoEINTR(可见select引起的阻塞为INTERRUOPTIBLE阻塞)

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

使用方法

1.将要监控的文件添加到文件描述符集(被读、写、异常监控的文件描述符集)

2.调用select开始监控

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

系统提供了4个宏对描述符集进行操作:
#include <sys/select.h>

void FD_SET(int fd, fd_set *fdset)

void FD_CLR(int fd, fd_set *fdset)

void FD_ZERO(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发生了变化。

: 

[plain]  view plain  copy
  1. FD_ZERO(&fds); //清空集合  
  2. FD_SET(fd1, &fds); //设置描述符  
  3. FD_SET(fd2, &fds); //设置描述符  
  4. maxfdp = fd1 + 1; //描述符最大值加1,假设fd1>fd2  
  5. switch(select(maxfdp, &fds, NULL,    NULL, &timeout))  
  6.     case -1: exit(-1);break;//select/ //错误,退出程序  
  7.     case 0:break;  
  8.     default: if( FD_ISSET(fd1, &fds)) //测试fd1是否可读  

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

范例

[plain]  view plain  copy
  1. static unsigned int mem_poll(struct file * filp, poll_table * wait)  
  2. {  
  3.     struct scull_pipe *dev = filp->private_data;  
  4.     unsigned int mask = 0;  
  5. /*把等待队列添加到poll_table*/  
  6.     poll_wait(filp, &dev->inq, wait);  
  7. /*返回掩码*/  
  8.     if(有数据可读)  
  9.        Mask = POLLIN | POLLRDNORM;//设备可读  
  10.     rerurn mask;  
  11. }  

工作原理

Poll方法并未让进程发生阻塞,而select方法却可以让进程发生阻塞,why?

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


 

自动创建设备文件

自动创建(2.4内核)

devfs_register(devfs_handle_tdir, const char *name, unsigned int flags, unsigned int major, unsigned intminor, umode_t mode, void * ops, void *info)

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

 

其中mode(创建模式)类似于mknod中的b/c

 

自动创建(2.6内核)

从linux2.6.13开始,devfs不复存在,udev成为devfs的替代。相比devfs,udev(mdev)存在于应用层。利用udev(mdev)来实现设备文件的自动创建很简单,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。

 

busy_box里默认没有udev(mdev),因此首先要配置busy_box,让其支持mdev,才能用mdev

 

   struct class * myclass=class_create(THIS_MODULE, “by_device_driver”);

   device_create(myclass, NULL,MKDEV(major_num, 0), NULL, “my_device”);

当驱动被加载时,udev(mdev)就会自动在/dev下创建my_device设备文件

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值