Linux驱动总结3- unlocked_ioctl和堵塞(waitqueue)读写函数的实现 【转】

转自:http://blog.chinaunix.net/uid-20937170-id-3033633.html

学习了驱动程序的设计,感觉在学习驱动的同时学习linux内核,也是很不错的过程哦,做了几个实验,该做一些总结,只有不停的作总结才能印象深刻。

我的平台是虚拟机,fedora14,内核版本为2.6.38.1.其中较之前的版本存在较大的差别,具体的实现已经在上一次总结中给出了。今天主要总结的是ioctl和堵塞读写函数的实现。

一、ioctl函数的实现

首先说明在2.6.36以后ioctl函数已经不再存在了,而是用unlocked_ioctl和compat_ioctl两个函数实现以前版本的ioctl函数。同时在参数方面也发生了一定程度的改变,去除了原来ioctl中的struct inode参数,同时改变了返回值。

但是驱动设计过程中存在的问题变化并不是很大,同样在应用程序设计中我们还是采用ioctl实现访问,而并不是unlocked_ioctl函数,因此我们还可以称之为ioctl函数的实现。

ioctl函数的实现主要是用来实现具体的硬件控制,采用相应的命令控制硬件的具体操作,这样就能使得硬件的操作不再是单调的读写操作。使得硬件的使用更加的方便。

ioctl函数实现主要包括两个部分,首先是命令的定义,然后才是ioctl函数的实现,命令的定义是采用一定的规则。

ioctl的命令主要用于应用程序通过该命令操作具体的硬件设备,实现具体的操作,在驱动中主要是对命令进行解析,通过switch-case语句实现不同命令的控制,进而实现不同的硬件操作。

ioctl函数的命令定义方法:

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

虽然其中没有指针的参数,但是通常采用arg传递指针参数。cmd是一个命令。每一个命令由一个整形数据构成(32bits),将一个命令分成四部分,每一部分实现具体的配置,设备类型(幻数)8bits,方向2bits,序号8bits,数据大小13/14bits。命令的实现实质上就是通过简单的移位操作,将各个部分组合起来而已。

一个命令的分布的大概情况如下:

|---方向位(31-30)|----数据长度(29-16)----------------|---------设备类型(15-8)------|----------序号(7-0)----------|

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

其中方向位主要是表示对设备的操作,比如读设备,写设备等操作以及读写设备等都具有一定的方向,2个bits只有4种方向。

数据长度表示每一次操作(读、写)数据的大小,一般而已每一个命令对应的数据大小都是一个固定的值,不会经常改变,14bits说明可以选择的数据长度最大为16k。

设备类型类似于主设备号(由于8bits,刚好组成一个字节,因此经常采用字符作为幻数,表示某一类设备的命令),用来区别不同的命令类型,也就是特定的设备类型对应特定的设备。序号主要是这一类命令中的具体某一个,类似于次设备号(256个命令),也就是一个设备支持的命令多达256个。

同时在内核中也存在具体的宏用来定义命令以及解析命令。

但是大部分的宏都只是定义具体的方向,其他的都需要设计者定义。

主要的宏如下:

#include

_IO(type,nr)                    表示定义一个没有方向的命令,

_IOR(type,nr,size)            表示定义一个类型为type,序号为nr,数据大小为size的读命令

_IOW(type,nr,size)           表示定义一个类型为type,序号为nr,数据大小为size的写命令

_IOWR(type,nr,size)         表示定义一个类型为type,序号为nr,数据大小为size的写读命令

通常的type可采用某一个字母或者数字作为设备命令类型。

是实际运用中通常采用如下的方法定义一个具体的命令:

  1. //头文件
  2. #include
  3. /*定义一系列的命令*/
  4. /*幻数,主要用于表示类型*/
  5. #define MAGIC_NUM ‘k‘
  6. /*打印命令*/
  7. #define MEMDEV_PRINTF _IO(MAGIC_NUM,1)
  8. /*从设备读一个int数据*/
  9. #define MEMDEV_READ _IOR(MAGIC_NUM,2,int)
  10. /*往设备写一个int数据*/
  11. #define MEMDEV_WRITE _IOW(MAGIC_NUM,3,int)
  12. /*最大的序列号*/
  13. #define MEM_MAX_CMD 3

还有对命令进行解析的宏,用来确定具体命令的四个部分(方向,大小,类型,序号)具体如下所示:

  1. /*确定命令的方向*/
  2. _IOC_DIR(nr)
  3. /*确定命令的类型*/
  4. _IOC_TYPE(nr)
  5. /*确定命令的序号*/
  6. _IOC_NR(nr)
  7. /*确定命令的大小*/
  8. _IOC_SIZE(nr)

上面的几个宏可以用来命令,实现命令正确性的检查。

ioctl的实现过程主要包括如下的过程:

1、命令的检测

2、指针参数的检测

3、命令的控制switch-case语句

1、命令的检测主要包括类型的检查,数据大小,序号的检测,通过结合上面的命令解析宏可以快速的确定。

  1. /*检查类型,幻数是否正确*/
  2. if(_IOC_TYPE(cmd)!=MAGIC_NUM)
  3. return -EINVAL;
  4. /*检测命令序号是否大于允许的最大序号*/
  5. if(_IOC_NR(cmd)> MEM_MAX_CMD)
  6. return -EINVAL;

2、主要是指针参数的检测。指针参数主要是因为内核空间和用户空间的差异性导致的,因此需要来自用户空间指针的有效性。使用copy_from_user,copy_to_user,get_user,put_user之类的函数时,由于函数会实现指针参量的检测,因此可以省略,但是采用__get_user(),__put_user()之类的函数时一定要进行检测。具体的检测方法如下所示:

  1. if(_IOC_DIR(cmd) & _IOC_READ)
  2. err = !access_ok(VERIFY_WRITE,(void *)args,_IOC_SIZE(cmd));
  3. else if(_IOC_DIR(cmd) & _IOC_WRITE)
  4.  err = !access_ok(VERIFY_READ,(void *)args,_IOC_SIZE(cmd));
  5. if(err)/*返回错误*/
  6. return -EFAULT;

当方向是读时,说明是从设备读数据到用户空间,因此要检测用户空间的指针是否可写,采用VERIFY_WRITE,而当方向是写时,说明是往设备中写数据,因此需要检测用户空间中的指针的可读性VERIFY_READ。检查通常采用access_ok()实现检测,第一个参数为读写,第二个为检测的指针,第三个为数据的大小。

3、命名的控制:

命令的控制主要是采用switch和case相结合实现的,这于window编程中的检测各种消息的实现方式是相同的。

  1. /*根据命令执行相应的操作*/
  2. switch(cmd)
  3. {
  4. case MEMDEV_PRINTF:
  5. printk("<--------CMD MEMDEV_PRINTF Done------------>\n\n");
  6. ...
  7. break;
  8. case MEMDEV_READ:
  9. ioarg = &mem_devp->data;
  10. ...
  11. ret = __put_user(ioarg,(int *)args);
  12. ioarg = 0;
  13. ...
  14. break;
  15. case MEMDEV_WRITE:
  16. ...
  17. ret = __get_user(ioarg,(int *)args);
  18. printk("<--------CMD MEMDEV_WRITE Done ioarg = %d--------->\n\n",ioarg);
  19. ioarg = 0;
  20. ...
  21. break;
  22. default:
  23. ret = -EINVAL;
  24. printk("<-------INVAL CMD--------->\n\n");
  25. break;
  26. }

这只是基本的框架结构,实际中根据具体的情况进行修改。这样就实现了基本的命令控制。

文件操作支持的集合如下:

  1. /*添加该模块的基本文件操作支持*/
  2. static const struct file_operations mem_fops =
  3. {
  4. /*结尾不是分号,注意其中的差别*/
  5. .owner = THIS_MODULE,
  6. .llseek = mem_llseek,
  7. .read = mem_read,
  8. .write = mem_write,
  9. .open = mem_open,
  10. .release = mem_release,
  11. /*添加新的操作支持*/
  12. .unlocked_ioctl = mem_ioctl,
  13. };

需要注意不是ioctl,而是unlocked_ioctl。

二、设备的堵塞读写方式实现,通常采用等待队列。

设备的堵塞读写方式,默认情况下的读写操作都是堵塞型的,具体的就是如果需要读数据,当设备中没有数据可读的时候应该等待设备中有设备再读,当往设备中写数据时,如果上一次的数据还没有被读完成,则不应该写入数据,就会导致进程的堵塞,等待数据可读写。但是在应用程序中也可以采用非堵塞型的方式进行读写。只要在打开文件的时候添加一个O_NONBLOCK,这样在不能读写的时候就会直接返回,而不会等待。

因此我们在实际设计驱动设备的同时需要考虑读写操作的堵塞方式。堵塞方式的设计主要是通过等待队列实现,通常是将等待队列(实质就是一个链表)的头作为设备数据结构的一部分。在设备初始化过程中初始化等待队列的头。最后在设备读写操作的实现添加相应的等待队列节点,并进行相应的控制。

等待队列的操作基本如下:

1、等待队列的头定义并初始化的过程如下:

方法一:

struct wait_queue_head_t mywaitqueue;

init_waitqueue_head(&mywaitqueue);

方法二:

DECLARE_WAIT_QUEUE_HEAD(mywaitqueue);

以上的两种都能实现定义和初始化等待队列头。

2、创建、移除一个等待队列的节点,并添加、移除相应的队列。

定义一个等待队列的节点:DECLARE_WAITQUEUE(wait,tsk)

其中tsk表示一个进程,可以采用current当前的进程。

添加到定义好的等待队列头中。

add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);

即:add_wait_queue(&mywaitqueue,&wait);

移除等待节点

remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);

即:remove_wait_queue(&mywaitqueue,&wait);

3、等待事件

wait_event(queue,condition);当condition为真时,等待队列头queue对应的队列被唤醒,否则继续堵塞。这种情况下不能被信号打断。

wait_event_interruptible(queue,condition);当condition为真时,等待队列头queue对应的队列被唤醒,否则继续堵塞。这种情况下能被信号打断。

4、唤醒等待队列

wait_up(wait_queue_head_t *q),唤醒该等待队列头对应的所有等待。

wait_up_interruptible(wait_queue_head_t *q)唤醒处于TASK_INTERRUPTIBLE的等待进程。

应该成对的使用。即wait_event于wait_up,而wait_event_interruptible与wait_up_interruptible。

  1. wait_event和wait_event_interruptible的实现都是采用宏的方式,都是一个重新调度的过程,如下所示:
  1. #define wait_event_interruptible(wq, condition)                \
  2. ({                                    \
  3. int __ret = 0;                            \
  4. if (!(condition))                        \
  5. __wait_event_interruptible(wq, condition, __ret);    \
  6. __ret;                                \
  7. })
  1. #define __wait_event_interruptible(wq, condition, ret)            \
  2. do {                                    \
  3. /*此处存在一个声明等待队列的语句,因此不需要再重新定义一个等待队列节点*/
  4. DEFINE_WAIT(__wait);                        \
  5. \
  6. for (;;) {                            \
  7. /*此处就相当于add_wait_queue()操作,具体参看代码如下所示*/
  8. prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);    \
  9. if (condition)                        \
  10. break;                        \
  11. if (!signal_pending(current)) {                \
  12. /*此处是调度,丢失CPU,因此需要wake_up函数唤醒当前的进程
  13. 根据定义可知,如果条件不满足,进程就失去CPU,能够跳出for循环的出口只有
  14. 1、当条件满足时2、当signal_pending(current)=1时。
  15. 1、就是满足条件,也就是说wake_up函数只是退出了schedule函数,
  16. 而真正退出函数还需要满足条件
  17. 2、说明进程可以被信号唤醒。也就是信号可能导致没有满足条件时就唤醒当前的进程。
  18. 这也是后面的代码采用while判断的原因.防止被信号唤醒。
  19. */
  20. schedule();                    \
  21. continue;                    \
  22. }                            \
  23. ret = -ERESTARTSYS;                    \
  24. break;                            \
  25. }                                \
  26. finish_wait(&wq, &__wait);                    \
  27. } while (0)

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

#define DEFINE_WAIT_FUNC(name, function) \

wait_queue_t name = { \

.private = current, \

.func = function, \

.task_list = LIST_HEAD_INIT((name).task_list), \

}

  1. void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
  2. {
  3. unsigned long flags;
  4. wait->flags &= ~WQ_FLAG_EXCLUSIVE;
  5. spin_lock_irqsave(&q->lock, flags);
  6. if (list_empty(&wait->task_list))
  7. /*添加节点到等待队列*/
  8. __add_wait_queue(q, wait);
  9. set_current_state(state);
  10. spin_unlock_irqrestore(&q->lock, flags);
  11. }
  12. 唤醒的操作也是类似的。
  13. #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

void __wake_up(wait_queue_head_t *q, unsigned int mode,

int nr_exclusive, void *key)

{

unsigned long flags;

spin_lock_irqsave(&q->lock, flags);

__wake_up_common(q, mode, nr_exclusive, 0, key);

spin_unlock_irqrestore(&q->lock, flags);

}

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,

int nr_exclusive, int wake_flags, void *key)

{

wait_queue_t *curr, *next;

list_for_each_entry_safe(curr, next, &q->task_list, task_list) {

unsigned flags = curr->flags;

if (curr->func(curr, mode, wake_flags, key) &&

(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

break;

}

}

等待队列通常用在驱动程序设计中的堵塞读写操作,并不需要手动的添加节点到队列中,直接调用即可实现,具体的实现方法如下:

1、在设备结构体中添加等待队列头,由于读写都需要堵塞,所以添加两个队列头,分别用来堵塞写操作,写操作。

  1. #include<linux/wait.h>
  2. struct mem_dev
  3. {
  4. char *data;
  5. unsigned long size;
  6. /*添加一个并行机制*/
  7. spinlock_t lock;
  8. /*添加一个等待队列t头*/
  9. wait_queue_head_t rdqueue;
  10. wait_queue_head_t wrqueue;
  11. };

2、然后在模块初始化中初始化队列头:

  1. /*初始化函数*/
  2. static int memdev_init(void)
  3. {
  4. ....
  5. for(i = 0; i < MEMDEV_NR_DEVS; i)
  6. {
  7. mem_devp[i].size = MEMDEV_SIZE;
  8. /*对设备的数据空间分配空间*/
  9. mem_devp[i].data = kmalloc(MEMDEV_SIZE,GFP_KERNEL);
  10. /*问题,没有进行错误的控制*/
  11. memset(mem_devp[i].data,0,MEMDEV_SIZE);
  12. /*初始化定义的互信息量*/
  13. //初始化定义的自旋锁ua
  14. spin_lock_init(&(mem_devp[i].lock));
  15. /*初始化两个等待队列头,需要注意必须用括号包含起来,使得优先级正确*/
  16. init_waitqueue_head(&(mem_devp[i].rdqueue));
  17. init_waitqueue_head(&(mem_devp[i].wrqueue));
  18. }
  19. ...
  20. }

3、确定一个具体的条件,比如数据有无,具体的条件根据实际的情况设计。

/*等待条件*/

static bool havedata = false;

4、在需要堵塞的读函数,写函数中分别实现堵塞,首先定义等待队列的节点,并添加到队列中去,然后等待事件的唤醒进程。但是由于读写操作的两个等待队列都是基于条件havedata的,所以在读完成以后需要唤醒写,写完成以后需要唤醒读操作,同时更新条件havedata,最后还要移除添加的等待队列节点。

  1. /*read函数的实现*/
  2. static ssize_t mem_read(struct file *filp,char __user *buf, size_t size,loff_t *ppos)
  3. {
  4. unsigned long p = *ppos;
  5. unsigned int count = size;
  6. int ret = 0;
  7. struct mem_dev *dev = filp->private_data;
  8. /*参数的检查,首先判断文件位置*/
  9. if(p >= MEMDEV_SIZE)
  10. return 0;
  11. /*改正文件大小*/
  12. if(count > MEMDEV_SIZE - p)
  13. count = MEMDEV_SIZE - p;

#if 0

  1. /*添加一个等待队列节点到当前进程中*/
  2. DECLARE_WAITQUEUE(wait_r,current);
  3. /*将节点添加到等待队列中*/
  4. add_wait_queue(&dev->rdqueue,&wait_r);
  5. /*添加等待队列,本来采用if即可,但是由于信号等可能导致等待队列的唤醒,因此采用循环,确保不会出现误判*/
  6. #endif
  7. while(!havedata)
  8. {
  9. /*判断用户是否设置为非堵塞模式读,告诉用户再读*/
  10. if(filp->f_flags & O_NONBLOCK)
  11. return -EAGAIN;
  12. /*依据条件havedata判断队列的状态,防止进程被信号唤醒*/
  13. wait_event_interruptible(dev->rdqueue,havedata);
  14. }
  15. spin_lock(&dev->lock);
  16. /*从内核读数据到用户空间,实质就通过private_data访问设备*/
  17. if(copy_to_user(buf,(void *)(dev->data p),count))
  18. {
  19. /*出错误*/
  20. ret = -EFAULT;
  21. }
  22. else
  23. {
  24. /*移动当前文件光标的位置*/
  25. *ppos = count;
  26. ret = count;
  27. printk(KERN_INFO "read %d bytes(s) from %d\n",count,p);
  28. }
  29. spin_unlock(&dev->lock);

#if 0

  1. /*将等待队列节点从读等待队列中移除*/
  2. remove_wait_queue(&dev->rdqueue,&wait_r);
  3. #endif
  4. /*更新条件havedate*/
  5. havedata = false;
  6. /*唤醒写等待队列*/
  7. wake_up_interruptible(&dev->wrqueue);
  8. return ret;
  9. }
  1. /*write函数的实现*/
  2. static ssize_t mem_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos)
  3. {
  4. unsigned long p = *ppos;
  5. unsigned int count = size;
  6. int ret = 0;
  7. /*获得设备结构体的指针*/
  8. struct mem_dev *dev = filp->private_data;
  9. /*检查参数的长度*/
  10. if(p >= MEMDEV_SIZE)
  11. return 0;
  12. if(count > MEMDEV_SIZE - p)
  13. count = MEMDEV_SIZE - p;

#if 0

  1. /*定义并初始化一个等待队列节点,添加到当前进程中*/
  2. DECLARE_WAITQUEUE(wait_w,current);
  3. /*将等待队列节点添加到等待队列中*/
  4. add_wait_queue(&dev->wrqueue,&wait_w);
  5. #endif
  6. /*添加写堵塞判断*/
  7. /*为何采用循环是为了防止信号等其他原因导致唤醒*/
  8. while(havedata)
  9. {
  10. /*如果是以非堵塞方式*/
  11. if(filp->f_flags & O_NONBLOCK)
  12. return -EAGAIN;
  13. /*分析源码发现,wait_event_interruptible 中存在DECLARE_WAITQUEUE和add_wait_queue的操作,因此不需要手动添加等待队列节点*/
  14. wait_event_interruptible(&dev->wrqueue,(!havedata));
  15. }
  16. spin_lock(&dev->lock);
  17. if(copy_from_user(dev->data p,buf,count))
  18. ret = -EFAULT;
  19. else
  20. {
  21. /*改变文件位置*/
  22. *ppos = count;
  23. ret = count;
  24. printk(KERN_INFO "writted %d bytes(s) from %d\n",count,p);
  25. }
  26. spin_unlock(&dev->lock);
  27. #if 0
  28. /*将该等待节点移除*/
  29. remove_wait_queue(&dev->wrqueue,&wait_w);
  30. #endif
  31. /*更新条件*/
  32. havedata = true;
  33. /*唤醒读等待队列*/
  34. wake_up_interruptible(&dev->rdqueue);
  35. return ret;
  36. }

5、应用程序采用两个不同的进程分别进行读、写,然后检测顺序是否可以调换,检查等待是否正常。

时间: 2024-07-29 07:35:36

Linux驱动总结3- unlocked_ioctl和堵塞(waitqueue)读写函数的实现 【转】的相关文章

Linux代码的重用与强行卸载Linux驱动

(一)Linux代码的重用 重用=静态重用(将要重用的代码放到其他的文件的头文件中声明)+动态重用(使用另外一个Linux驱动中的资源,例如函数.变量.宏等) 1.编译是由多个文件组成的Linux驱动(静态重用) 对于复杂的Linux驱动,需要使用多个源代码文件存放不同的功能代码,这样做有利于代码分类和管理,那么就不得不编译多个源代码文件,最终生成.ko文件或编译进Linux内核 下面,就介绍将3个.c文件分别编译为3个.o文件,并将这3个.o文件链接(link)成一个.ko文件——静态重用 假

第八章:Linux代码重用、Linux驱动强行卸载

(一)Linux代码的重用 重用=静态重用(将要重用的代码放到其他的文件的头文件中声明)+动态重用(使用另外一个Linux驱动中的资源,例如函数.变量.宏等) 1.编译是由多个文件组成的Linux驱动,即静态重用 对于复杂的Linux驱动,需要使用多个源代码文件存放不同的功能代码,这样做有利于代码分类和管理,那么就不得不编译多个源代码文件,最终生成.ko文件或编译进Linux内核 下面,就举例介绍将3个.c文件分别编译为3个.o文件,并将这3个.o文件链接(link)成一个.ko文件——静态重用

浅谈Linux驱动到设备模型再到设备树(总结)

1.最初Linux驱动架构 Linux驱动会在初始化函数中向内核注册file_operations结构体,结构体里面就包含一些基本的open,close函数.Linux驱动中也会去实现这些open函数.并且相对应的硬件信息也在这个驱动中.以LED为例,驱动程序中会将LED的引脚地址映射成虚拟地址,然后在open函数里面进行写操作. 当APP调用open函数的时候,就会通过一系列转换,最后调用到驱动中的open函数.(这边就不具体描述APP怎么调用到驱动中的open函数) 原文地址:https:/

【Linux 驱动】设备驱动程序再理解

学习设备驱动编程也有一段时间了,也写过了几个驱动程序,因此有对设备驱动程序有了一些新的理解和认识,总结一下.学习设备驱动编程也有一段时间了,也写过了几个驱动程序,因此有对设备驱动程序有了一些新的理解和认识,总结一下. ★什么是驱动程序 刚开始学习设备驱动程序的时候,产生了许多的问题.什么是驱动程序?驱动程序是干嘛的?它是如何工作的?它又是如何跟操作系统联系起来的?一系列的问题,现在有些地方还是不一定清楚,但是相比起刚开始的那个阶段,感觉自己还是清楚了很多. 设备驱动程序说白了(实质)就是为应用程

嵌入式linux驱动开发之点亮led未遂(驱动编程思想之初体验)

有了上两篇文章的基础,我们就可以开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的住,不然真像一些人说的,学了一年嵌入式感觉还没找到门. 另外实践很重要,一年多以前就知道了arm,那时整天用单片机的思维去yy着arm,直到前段时间弄来一块arm板,烧上linux系统后才知道,坑呀!根本不是那回事,所以实践是学习计算机类最重要的基本素质,如果整天看书,那基本上

linux驱动初探之字符驱动

关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3.0的内核,exynos4412开发板. 首先,打开平台文件,此开发板的平台文件是arch\arm\mach-exynos\mach-itop4412.c,不同平台位置是不一样的. 申明一下设备信息,这里以编译进kernel为例 1 #ifdef CONFIG_HELLO_CHAR_CTL 2 str

Linux驱动调试中关于ioctl的问题

1.提示:错误: 初始值设定项里有未知的字段'ioctl' 2.6以后的内核中file_operation结构体已经删除了ioctl函数,取代的是:long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 1 struct file_operations { 2 struct modul

linux驱动---等待队列、工作队列、Tasklets【转】

转自:https://blog.csdn.net/ezimu/article/details/54851148 概述: 等待队列.工作队列.Tasklet都是linux驱动很重要的API,下面主要从用法上来讲述如何使用API. 应用场景: 等待队列(waitqueue) linux驱动中,阻塞一般就是用等待队列来实现,将进程停止在此处并睡眠下,直到条件满足时,才可通过此处,继续运行.在睡眠等待期间,wake up时,唤起来检查条件,条件满足解除阻塞,不满足继续睡下去. 工作队列(workqueu

基于S3C2440的嵌入式Linux驱动——看门狗(watchdog)驱动解读

本文将介绍看门狗驱动的实现. 目标平台:TQ2440 CPU:s3c2440 内核版本:2.6.30 1. 看门狗概述 看门狗其实就是一个定时器,当该定时器溢出前必须对看门狗进行"喂狗",如果不这样做,定时器溢出后则将复位CPU. 因此,看门狗通常用于对处于异常状态的CPU进行复位. 具体的概念请自行百度. 2. S3C2440看门狗 s3c2440的看门狗的原理框图如下: 可以看出,看门狗定时器的频率由PCLK提供,其预分频器最大取值为255+1:另外,通过MUX,可以进一步降低频率