Linux设备驱动程序学习[5]—高级字符驱动程序操作2
一、 休眠
进程被置为休眠,意味着它被标识为处于一个特殊的状态并且从调度器的运行队列中移走。这个进程将不被在任何 CPU 上调度,即将不会运行。 直到发生某些事情改变了那个状态。安全地进入休眠的两条
规则:
(1) 永远不要在原子上下文中进入休眠,即当驱动在持有一个自旋锁、seqlock或者 RCU 锁时不能睡眠;关闭中断也不能睡眠。持有一个信号量时休眠是合法的,但你应当仔细查看代码:如果代码在持有一个信号量时睡眠,任何其他的等待这个信号量的线程也会休眠。因此发生在持有信号量时的休眠必须短暂,而且决不能阻塞那个将最终唤醒你的进程。
(2)当进程被唤醒,它并不知道休眠了多长时间以及休眠时发生什么;也不知道是否另有进程也在休眠等待同一事件,且那个进程可能在它之前醒来并获取了所等待的资源。所以不能对唤醒后的系统状态做任何的假设,并必须重新检查等待条件来确保正确的响应。
除非确信其他进程会在其他地方唤醒休眠的进程,否则也不能睡眠。使进程可被找到意味着:需要维护一个称为等待队列的数据结构。它是一个进程链表,其中饱含了等待某个特定事件的所有进程。在 Linux 中, 一个等待队列由一个wait_queue_head_t 结构体来管理,其定义在中。
wait_queue_head_t 类型的数据结构非常简单:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
它包含一个自旋锁和一个链表。这个链表是一个等待队列入口,它被声明做 wait_queue_t。wait_queue_head_t包含关于睡眠进程的信息和它想怎样被唤醒。
自旋锁 现在还不理解什么意思,上一章没有看。
简单休眠(其实是高级休眠的宏)
Linux 内核中最简单的休眠方式是称为 wait_event的宏(及其变种),它实现了休眠和进程等待的条件的
检查。形式如下:
wait_event(queue, condition)/*不可中断休眠,不推荐*/
wait_event_interruptible(queue, condition)/*推荐,返回非零值意味着休眠被中断,且驱动应返回 -ERESTARTSYS*/
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
/*有限的时间的休眠;若超时,则不管条件为何值返回0,*/
唤醒休眠进程的函数称为 wake_up,形式如下:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
惯例: 用 wake_up 唤醒 wait_event ;
用 wake_up_interruptible 唤醒wait_event_interruptible
上述读书笔记为前辈整理,懒得整理借来用了,谢谢前辈!
实验例程则按照自己阅读《LDD3》写成,由于休眠这部分没有涉及scull设备的内存操作,故抛弃了scull设备,重新建立sleepy设备,进程的休眠与唤醒通过标志位flag控制;
Read时:(flag != 0)为假 –> 休眠;(flag != 0)为真 –> 唤醒;
从上述可以看出wait_event_interruptible(queue, condition)宏的使用,在condition这个布尔表达式为真之前进程保持休眠;即condition为假->休眠,condition为真->唤醒;
Write时:flag = 1;即可唤醒读的进程
实验程序:
/* *File Name :sleepy.c *Function :test character device driver, only register one scull *Author :gufeiyang *From :<> *Time :2010-2-12 home yunnan */ #include <linux/module.h> /*EXPORT_SYMBOL_GPL ())*/ #include <linux/moduleparam.h> /*module_param(variable, type, perm); */ #include <linux/init.h> /*module_init(init_function); module_exit(cleanup_function);*/ #include <linux/kernel.h> /*int printk(const char * fmt, ...);*/ #include <linux/types.h> /* size_t */ #include <linux/kdev_t.h> /*dev_t*/ #include <linux/fs.h> /* everything... */ #include <linux/cdev.h> /*struct cdev *cdev_alloc(void); */ #include <linux/device.h> /*struct class*/ #include <linux/sched.h> /* current and everything */ #include <linux/wait.h> #define DEV_NAME "sleepy" #define SLEEP_MAJOR 0 static int sleepy_major = SLEEP_MAJOR; static int sleepy_minor = 0; static struct cdev *sl_cdev; static struct class *scull_class; static DECLARE_WAIT_QUEUE_HEAD(wq); static int flag = 0; static ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count,loff_t *pos) { printk(KERN_WARNING "process %i (%s) awakening the readers...\n", current->pid, current->comm); flag = 1; wake_up_interruptible(&wq); return count; /* succeed, to avoid retrial */ } static ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos) { printk(KERN_WARNING "process %i (%s) going to sleep\n", current->pid, current->comm); wait_event_interruptible(wq, flag != 0); flag = 0; printk(KERN_WARNING "awoken %i (%s)\n", current->pid, current->comm); return 0; /* EOF */ } static struct file_operations sleepy_fops = { .owner = THIS_MODULE, .read = sleepy_read, .write = sleepy_write // .open = scull_open,
// .release = scull_release
}; static int __init sleepy_init(void) { int result,err; dev_t dev; /* * Get a range of minor numbers to work with, asking for a dynamic * major unless directed otherwise at load time. */ if (sleepy_major) { dev = MKDEV(sleepy_major, sleepy_minor); result = register_chrdev_region(dev, 1, DEV_NAME); } else { result = alloc_chrdev_region(&dev, sleepy_minor, 1, DEV_NAME); sleepy_major = MAJOR(dev); } if (result < 0) { printk(KERN_WARNING "scull: can't get major %d\n", sleepy_major); return result; }else { printk(KERN_WARNING"Scull_major = %d\n", sleepy_major); } sl_cdev = cdev_alloc(); // sl_cdev->ops = &sleepy_fops;
cdev_init(sl_cdev, &sleepy_fops); sl_cdev->owner = THIS_MODULE; err = cdev_add(sl_cdev, dev, 1) ; if (err) { printk(KERN_NOTICE "Error %d adding sleepy_cdev", err); } scull_class = class_create(THIS_MODULE, DEV_NAME); if(IS_ERR(scull_class)) { printk(KERN_ALERT"Err:faile in scull_class!\n"); return -1; } /*创建设备节点,名字为DEVICE_NAME ,主设备号用上面动态生成的dev*/ class_device_create(scull_class, NULL, dev, NULL, DEV_NAME); printk(KERN_WARNING"Module Initialed!\nChracter Device Driver Start!\n"); return 0; } static void __exit sleey_exit(void) { int devno = MKDEV(sleepy_major, sleepy_minor ); unregister_chrdev_region(devno, 1); cdev_del(sl_cdev); class_device_destroy(scull_class, devno); class_destroy(scull_class); printk(KERN_WARNING"Module exit!\nChracter Device Driver End!\n"); } module_init(sleepy_init); module_exit(sleey_exit); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("CHARACTER DEVICE DRVER"); MODULE_AUTHOR("gufeiyang@2010-02-12"); /* *File Name :sleepy.c *Function :test character device driver, only register one scull *Author :gufeiyang *From :<> *Time :2010-2-12 home yunnan */ #include <linux/module.h> /*EXPORT_SYMBOL_GPL ())*/ #include <linux/moduleparam.h> /*module_param(variable, type, perm); */ #include <linux/init.h> /*module_init(init_function); odule_exit(cleanup_function); */ #include <linux/kernel.h> /*int printk(const char * fmt, ...);*/ #include <linux/types.h> /* size_t */ #include <linux/kdev_t.h> /*dev_t*/ #include <linux/fs.h> /* everything... */ #include <linux/cdev.h> /*struct cdev *cdev_alloc(void); */ #include <linux/device.h> /*struct class*/ #include <linux/sched.h> /* current and everything */ #include <linux/wait.h> #define DEV_NAME "sleepy" #define SLEEP_MAJOR 0 static int sleepy_major = SLEEP_MAJOR; static int sleepy_minor = 0; static struct cdev *sl_cdev; static struct class *scull_class; static DECLARE_WAIT_QUEUE_HEAD(wq); static int flag = 0; static ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count,loff_t *pos) { printk(KERN_DEBUG "process %i (%s) awakening the readers...\n", current->pid, current->comm); flag = 1; wake_up_interruptible(&wq); return count; /* succeed, to avoid retrial */ } static ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos) { printk(KERN_DEBUG "process %i (%s) going to sleep\n", current->pid, current->comm); wait_event_interruptible(wq, flag != 0); flag = 0; printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm); return 0; /* EOF */ } static struct file_operations sleepy_fops = { .owner = THIS_MODULE, .read = sleepy_read, .write = sleepy_write // .open = scull_open,
// .release = scull_release
}; static int __init sleepy_init(void) { int result,err; dev_t dev; /* * Get a range of minor numbers to work with, asking for a dynamic * major unless directed otherwise at load time. */ if (sleepy_major) { dev = MKDEV(sleepy_major, sleepy_minor); result = register_chrdev_region(dev, 1, DEV_NAME); } else { result = alloc_chrdev_region(&dev, sleepy_minor, 1, DEV_NAME); sleepy_major = MAJOR(dev); } if (result < 0) { printk(KERN_WARNING "scull: can't get major %d\n", sleepy_major); return result; }else { printk(KERN_WARNING"Scull_major = %d\n", sleepy_major); } sl_cdev = cdev_alloc(); // sl_cdev->ops = &sleepy_fops;
cdev_init(sl_cdev, &sleepy_fops); sl_cdev->owner = THIS_MODULE; err = cdev_add(sl_cdev, dev, 1) ; if (err) { printk(KERN_NOTICE "Error %d adding sleepy_cdev", err); } scull_class = class_create(THIS_MODULE, DEV_NAME); if(IS_ERR(scull_class)) { printk(KERN_ALERT"Err:faile in scull_class!\n"); return -1; } /*创建设备节点,名字为DEVICE_NAME ,主设备号用上面动态生成的dev*/ class_device_create(scull_class, NULL, dev, NULL, DEV_NAME); //PS:一时大意将DEV写成主设备号了,生成的设备节点与实际不符,故出现应用程序打开设备错误!
printk(KERN_WARNING"Module Initialed!\nChracter Device Driver Start!\n"); return 0; } static void __exit sleey_exit(void) { int devno = MKDEV(sleepy_major, sleepy_minor ); unregister_chrdev_region(devno, 1); cdev_del(sl_cdev); class_device_destroy(scull_class, devno); class_destroy(scull_class); printk(KERN_WARNING"Module exit!\nChracter Device Driver End!\n"); } module_init(sleepy_init); module_exit(sleey_exit); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("CHARACTER DEVICE DRVER"); MODULE_AUTHOR("gufeiyang@2010-02-12");。 /*sleepy-test-w-r.c*/ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h>
#define SLEEPY_W int main() { int fd,code; #ifdef SLEEPY_W if((fd = open("/dev/sleepy",O_WRONLY)) == -1) { perror("Open"); return -1; } else { printf("File Opened!\n"); } if((code = write(fd, NULL, 0)) < 0) { perror("Write"); } else { printf("write ok! code = %d\n", code); } close(fd); #endif #ifdef SLEEPY_R if((fd = open("/dev/sleepy",O_RDONLY)) == -1) { perror("Open"); return -1; } else { printf("File Opened!\n"); } if((code = read(fd, NULL, 0)) < 0) { perror("Read"); } else { printf("read ok! code = %d\n", code); } close(fd); #endif return 0; }
|
实验结果如下所示:
[root@gfy-S3C2440 /tmp]# ./sleepy-testr &
[root@gfy-S3C2440 /tmp]# File Opened!
process 930 (sleepy-testr) going to sleep
[root@gfy-S3C2440 /tmp]# ps
PID USER VSZ STAT COMMAND
1 root 2072 S init
2 root 0 SW< [kthreadd]
3 root 0 SW< [ksoftirqd/0]
4 root 0 SW< [watchdog/0]
5 root 0 SW< [events/0]
6 root 0 SW< [khelper]
68 root 0 SW< [kblockd/0]
112 root 0 SW< [kswapd0]
113 root 0 SW< [aio/0]
753 root 0 SW< [mtdblockd]
754 root 0 SW< [ftld]
781 root 0 SW< [kpsmoused]
792 root 0 SW< [rpciod/0]
815 root 2072 S /usr/sbin/telnetd -l /bin/login
818 root 2076 S -/bin/sh
823 nobody 3364 S boa
930 root 700 S ./sleepy-testr #进程已经睡着,初始flag = 0 ,condition为假
931 root 2076 R ps
[root@gfy-S3C2440 /tmp]# ./sleepy-testw吧
File Opened!
process 932 (sleepy-testw) awakening the readers...
awoken 930 (sleepy-testr)
read ok! code = 0
write ok! code = 0
[1] + Done ./sleepy-testr
[root@gfy-S3C2440 /tmp]# ./sleepy-testr &
[root@gfy-S3C2440 /tmp]# File Opened!
process 933 (sleepy-testr) going to sleep
[root@gfy-S3C2440 /tmp]# ./sleepy-testr &
[root@gfy-S3C2440 /tmp]# File Opened!
process 934 (sleepy-testr) going to sleep
[root@gfy-S3C2440 /tmp]# ./sleepy-testr &
[root@gfy-S3C2440 /tmp]# File Opened!
process 935 (sleepy-testr) going to sleep
[root@gfy-S3C2440 /tmp]# ps
PID USER VSZ STAT COMMAND
1 root 2072 S init
2 root 0 SW< [kthreadd]
3 root 0 SW< [ksoftirqd/0]
753 root 0 SW< [mtdblockd]
754 root 0 SW< [ftld]
781 root 0 SW< [kpsmoused]
792 root 0 SW< [rpciod/0]
815 root 2072 S /usr/sbin/telnetd -l /bin/login
818 root 2076 S -/bin/sh
823 nobody 3364 S boa
933 root 700 S ./sleepy-testr
934 root 700 S ./sleepy-testr
935 root 700 S ./sleepy-testr
936 root 2076 R ps
[root@gfy-S3C2440 /tmp]# ./sleepy-testw
File Opened!
process 937 (sleepy-testw) awakening the readers...
awoken 933 (sleepy-testr)
read ok! code = 0
write ok! code = 0
[1] Done ./sleepy-testr
[root@gfy-S3C2440 /tmp]# ./sleepy-testw
File Opened!
process 938 (sleepy-testw) awakening the readers...
awoken 934 (sleepy-testr)
read ok! code = 0
write ok! code = 0
[2] - Done ./sleepy-testr
[root@gfy-S3C2440 /tmp]# ./sleepy-testw
File Opened!
process 939 (sleepy-testw) awakening the readers...
awoken 935 (sleepy-testr)
read ok! code = 0
write ok! code = 0
[3] + Done ./sleepy-testr
[root@gfy-S3C2440 /tmp]#
上述实验现象用《LDD3》上的话来说就是:读者也许会认为当两个进程在等待时调用sleepy_write(),当一个进程被唤醒时会将flag重置为0,第二个被唤醒的进程会立即进入睡眠状态。在单处理器系统中,这几乎是始终会发生的,正如上述实验结果所示。但是,我们必须理解并不是永远会发生这种情况,wake_up_interruptible调用会唤醒两个休眠的进程,而在重置flag之前,这两个进程完全有可能注意到标志位非零。
二、 阻塞和非阻塞操作
进行数据的读或者写的时候,如果设备或者设备的数据没有准备好的时候,驱动程序就应该是否进行非阻塞 I/O还是阻塞 I/O操作。
全功能的 read 和 write 方法涉及到进程可以决定是进行非阻塞 I/O还是阻塞 I/O操作。明确的非阻塞 I/O 由 filp->f_flags 中的 O_NONBLOCK 标志来指示(定义再 ,被 自动包含)。
不止是read和write方法上有O_NONBLOCK操作,O_NONBLOCK在open方法中也是很有意义的。,后面的章节会涉及此方面内容。
只有read、write和open文件操作受非阻塞标志O_NONBLOCK的影响。
一个阻塞I\O的例子
这个例子来自 scullpipe 驱动; 它是 scull 的一个特殊形式, 实现了一个象管道的设备。
该设备驱动程序使用了一个包含两个等待队列和一个缓冲区的设备结构,缓冲区大小可以用通常的方式配置(编译或加载),如下:
struct scull_pipe
{
wait_queue_head_t inq, outq; /* read and write queues */
char *buffer, *end; /* begin of buf, end of buf */
int buffersize; /* used in pointer arithmetic */
char *rp, *wp; /* where to read, where to write */
int nreaders, nwriters; /* number of openings for r/w */
struct fasync_struct *async_queue; /* asynchronous readers 异步读取者*/
struct semaphore sem; /* mutual exclusion semaphore 互斥信号量 */
struct cdev cdev; /* Char device structure */
};
Read实现负责阻塞和非阻塞型输入,如下:
static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count, loff_t
*f_pos)
{
struct scull_pipe *dev = filp->private_data;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
while (dev->rp == dev->wp)
{ /* nothing to read */
up(&dev->sem); /* release the lock */
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
PDEBUG("\"%s\" reading: going to sleep\n", current->comm);
if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */ /* otherwise loop, but first reacquire the lock */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
/* ok, data is there, return something */
if (dev->wp > dev->rp)
count = min(count, (size_t)(dev->wp - dev->rp));
else /* the write pointer has wrapped, return data up to dev->end */
count = min(count, (size_t)(dev->end - dev->rp));
if (copy_to_user(buf, dev->rp, count))
{
up (&dev->sem);
return -EFAULT;
}
dev->rp += count;
if (dev->rp == dev->end)
dev->rp = dev->buffer; /* wrapped */
up (&dev->sem);
/* finally, awake any writers and return */
wake_up_interruptible(&dev->outq);
PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);
return count;
}
好,现在需要好好理解上述阻塞原理,我还未完全理解,只把程序整理出来了,应用程序就用的前辈的,由于驱动程序我是在原先的scull设备里面把read方法修改成了阻塞方式,所以只生成了一个设备节点,应用程序里面就需要删除一个节点,驱动和应用程序源码如下:
PS:我看书的思路是,先把整体思路看清晰了,然后就开始构建代码,当别人的应用程序能完全在我构建的驱动上运行,再去分析细节地方;