目录
proc文件操作
非阻塞型I/O
阻塞型I/O
proc文件操作
proc 文件系统是一种伪文件系统,这种文件系统不存在于磁盘上,只存在于内存中只有内核运行时才会动态生成里面的内容。这个文件系统通常挂载在/proc 目录下,是核开发者向用户导出信息的常用方式,比如我们之前看到的/proc/devices 文件。在系统中有的这种文件也可写,这可以在不重新编译内核以及不重新启动系统的情况下改变内核的行为。之前驱动开发者经常使用该文件系统来对驱动进行调试,但是随着 proc 文件系统里的内容增多,已不推荐这种方式,对硬件来讲,取而代之的是 sysfs 文件系统,后面会进行学习。不过某些时候,驱动开发者还是会使用这个接口,比如只想查看当前的串口波特率信息和帧格式,而不想为之编写一个应用程序的时候,这也可以作为一种快速诊断故障的方案。这里不详细讨论 proc 文件系统,在之后会详细讨论对硬件设备更有用的 sysfs 文件系统,所以仅以一个示例来演示 proc 接口的使用。
Linux中的/proc文件系统详解(C/C++代码实现)_程序猿编码的博客-CSDN博客
https://www.cnblogs.com/dongzhuangdian/p/11366910.html
find . -type f |xargs grep -i "proc_ops"
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/ipmi_smi.h>
#include "vser.h"
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"
struct vser_dev {
unsigned int baud;
struct option opt;
struct cdev cdev;
struct proc_dir_entry *pdir;
struct proc_dir_entry *pdat;
};
DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;
static int vser_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{
int ret;
unsigned int copied = 0;
ret = kfifo_to_user(&vsfifo, buf, count, &copied);
return ret == 0 ? copied : ret;
}
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
int ret;
unsigned int copied = 0;
ret = kfifo_from_user(&vsfifo, buf, count, &copied);
return ret == 0 ? copied : ret;
}
static long vser_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
if(_IOC_TYPE(cmd) != VS_MAGIC)
return -ENOTTY;
switch (cmd) {
case VS_SET_BAUD:
vsdev.baud = arg;
break;
case VS_GET_BAUD:
arg = vsdev.baud;
break;
case VS_SET_FFMT:
if(copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
return -EFAULT;
break;
case VS_GET_FFMT:
if(copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))
return -EFAULT;
break;
default:
return -ENOTTY;
}
return 0;
}
static int dat_show(struct seq_file *m, void *v)
{
struct vser_dev *dev = m->private;
seq_printf(m, "baudrate: %d\n", dev->baud);
return seq_printf(m, "frame format: %d%c%d\n", dev->opt.datab, dev->opt.parity == 0 ? 'N' : dev->opt.parity == 1 ? 'O' : 'E',dev->opt.stopb);
}
static int proc_open(struct inode *inode, struct file *file)
{
return single_open(file, dat_show, PDE_DATA(inode));
}
static struct file_operations proc_ops = {
.owner = THIS_MODULE,
.open = proc_open,
.release = single_release,
.read = seq_read,
.llseek = seq_lseek,
};
#if 0
static struct file_operations proc_vser = {
.owner = THIS_MODULE,
.open = proc_open,
.release = single_release,
.read = seq_read,
.llseek = seq_lseek,
};
#endif
static int __init vser_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR,VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if(ret)
goto reg_err;
cdev_init(&vsdev.cdev, &proc_ops);
vsdev.cdev.owner = THIS_MODULE;
vsdev.baud = 115200;
vsdev.opt.datab = 8;
vsdev.opt.parity = 0;
vsdev.opt.stopb = 1;
ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);
if (ret)
goto add_err;
vsdev.pdir = proc_mkdir("vser", NULL);
if(!vsdev.pdir)
goto dir_err;
vsdev.pdat = proc_create_data("info", 0, vsdev.pdir, &proc_ops, &vsdev);
if(!vsdev.pdat)
goto dat_err;
return 0;
dat_err:
remove_proc_entry("vser", NULL);
dir_err:
cdev_del(&vsdev.cdev);
add_err:
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}
static void __exit vser_exit(void)
{
dev_t dev;
dev = MKDEV(VSER_MAJOR,VSER_MINOR);
cdev_del(&vsdev.cdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");
#ifndef _VSER_H
#define _VSER_H
struct option {
unsigned int datab;
unsigned int parity;
unsigned int stopb;
};
#define VS_MAGIC 's'
#define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int)
#define VS_GET_BAUD _IOW(VS_MAGIC, 1, unsigned int)
#define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option)
#define VS_GET_FFMT _IOW(VS_MAGIC, 3, struct option)
#endif
在vser_dev结构中添加了两个struct proc_dir_entry类型的指针成员,我们要先在/proc目录下建立一个vser目录,再在vser目录下建立一个info文件,那么这两个指针分别指向创建的目录和文件的目录项。代码第 142 行至第148 行,分别调用proc_mkdir 和 proc_create_data 创建了这个目录和文件,proc_mkdir 第二个参数为 NULL,表示在/proc目录下创建目录,第一个参数是目录的名字。proc_create_data 的第一个参数是文件的名字,第二个参数是权限,第三个参数是目录的目录项指针,第四个参数是该文件的操作方法集合,第五个参数是该文件的私有数据。proc_ops 是该文件的操作方法集合,其中release、read、llseek 都使用内核已有的函数来实现,而 open 用自定义的方法 proc_open来实现,proc_open 又调用了 single_open 来辅助实现,并且指定了对文件读操作更具体的实现。dat_show 是这部分代码中最关键的内容。代码第 100 行首先从私有数据中获得设备结构地址(这是在 proc_create_data 函数调用时传递的),然后使用 seq_printf动态地产生被读取文件的内容。
驱动实现后,可以使用下面的命令来进行验证
非阻塞型I/O
设备不一定随时都能给用户提供服务,这就有了资源可用和不可用两种状态。比如对于虚拟串口设备来说,当用户想要读取串口中的数据时,如果FIFO 中没有数据,那么设备对读进程来讲就是资源不可用;但是对于写进程来说,此时资源是可用的,因为有剩余的空间供写进程写入数据,即对写进程来说,当 FIFO 为满时,资源是不可用的。当资源不可用时,应用程序和驱动一起的各种配合就组成了多种I/O模型。如果应用程序以非阻塞的方式打开设备文件,当资源不可用时,驱动就应该立即返回,并用一个错误码 EAGAIN来通知应用程序此时资源不可用,应用程序应该稍后再尝试。对于这样的方式,驱动程序的读写接口代码应该在前面的基础上进行修改,如下所示.
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/ipmi_smi.h>
#include "vser.h"
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"
struct vser_dev {
unsigned int baud;
struct option opt;
struct cdev cdev;
struct proc_dir_entry *pdir;
struct proc_dir_entry *pdat;
};
DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;
static int vser_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{
int ret;
unsigned int copied = 0;
if (kfifo_is_empty(&vsfifo))
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
ret = kfifo_to_user(&vsfifo, buf, count, &copied);
return ret == 0 ? copied : ret;
}
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
int ret;
unsigned int copied = 0;
if (kfifo_is_full(&vsfifo))
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
ret = kfifo_from_user(&vsfifo, buf, count, &copied);
return ret == 0 ? copied : ret;
}
static long vser_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
if(_IOC_TYPE(cmd) != VS_MAGIC)
return -ENOTTY;
switch (cmd) {
case VS_SET_BAUD:
vsdev.baud = arg;
break;
case VS_GET_BAUD:
arg = vsdev.baud;
break;
case VS_SET_FFMT:
if(copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
return -EFAULT;
break;
case VS_GET_FFMT:
if(copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))
return -EFAULT;
break;
default:
return -ENOTTY;
}
return 0;
}
static int dat_show(struct seq_file *m, void *v)
{
struct vser_dev *dev = m->private;
seq_printf(m, "baudrate: %d\n", dev->baud);
return seq_printf(m, "frame format: %d%c%d\n", dev->opt.datab, dev->opt.parity == 0 ? 'N' : dev->opt.parity == 1 ? 'O' : 'E',dev->opt.stopb);
}
static int proc_open(struct inode *inode, struct file *file)
{
return single_open(file, dat_show, PDE_DATA(inode));
}
#if 0
static struct file_operations proc_ops = {
.owner = THIS_MODULE,
.open = proc_open,
.release = single_release,
.read = seq_read,
.llseek = seq_lseek,
};
#else
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
.unlocked_ioctl = vser_ioctl,
};
#endif
static int __init vser_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR,VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if(ret)
goto reg_err;
cdev_init(&vsdev.cdev, &vser_ops);
vsdev.cdev.owner = THIS_MODULE;
vsdev.baud = 115200;
vsdev.opt.datab = 8;
vsdev.opt.parity = 0;
vsdev.opt.stopb = 1;
ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);
if (ret)
goto add_err;
vsdev.pdir = proc_mkdir("vser", NULL);
if(!vsdev.pdir)
goto dir_err;
return 0;
dir_err:
cdev_del(&vsdev.cdev);
add_err:
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}
static void __exit vser_exit(void)
{
dev_t dev;
dev = MKDEV(VSER_MAJOR,VSER_MINOR);
cdev_del(&vsdev.cdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");
(这个代码改了我好久,可能还有小bug不过结果对了。书里代码都不全。不是少头文件就是有的点没说。不太适合新手,适合比新手强一丢丢,知道驱动开发大概流程的同学。)
驱动代码的第 43行用kfifo_is_empty判断FIFO 是否为空,第57行用kfifo_is_full判断FIFO是否为满,这是操作 kfifo 的两个宏,其参数都是 kfifo 的地址。第 44 行和第58行判断设备文件是否以非阻塞的方式打开,如果是,并且资源不可用,则返回 EAGAIN错误码。
测试程序代码的第20行用ONONBLOCK标志来表示以非阻塞方式打开设备文件,并首先开始读虚拟串口,此时 FIFO 中没有数据,所以函数立即返回,并报告“Resource temporarily unavailable”的错误,然后程序向虚拟串口写入了 32个字节的数据将 FIFO 填满,最后再发起一次写操作,因为没有剩余的空间来容纳数据,所以第二次写操作也返回资源不可用的错误。
阻塞型I/O
理解了非阻塞型 I/O,再来学习阻塞型 IO 就比较容易了。当进程以阻塞的方式打开设备文件时(默认的方式),如果资源不可用,那么进程阻塞,也就是进程休眠。具体来讲就是,如果进程发现资源不可用,主动将自己的状态设置为TASK_UNINTERRUPTIBLE(task_uninterruptible)或 TASK_INTERRUPTIBLE(可中断),然后将自己加入一个驱动所维护的等待队列中,最后调用schedule 主动放弃 CPU,操作系统将之从运行队列上移除,并调度其他的进程执行(当然,具体的过程要稍微比这个复杂一些,比如在这个过程中判断是否接收到信号,是否有排他限制等)。对于这样一个比较谦让的进程,内核是非常喜欢的,这也是将这种 I/O高级I/O操作方式设置为默认方式的原因。相比于非阻寒型 IO,其最大的优点就是,资源不可用时,不占用CPU的时间,而非阻塞型I/O 必须要定期尝试,看看资源是否可以获得,这对于键盘和鼠标这类设备来讲,其效率是非常低的。但是阻塞型 I/O 也有一个明显的缺点,那就是进程在休眠期间再也不能做其他的事了。
既然有休眠,就应该有对应的唤醒操作,否则进程将会一直休眠下去。驱动程序应该在资源可用时负责执行唤醒操作,比如读进程休眠了,那么对于虚拟串口而言,写进程就应该负责唤醒操作,在真实的串口设备中,通常应该是中断处理程序负责唤醒。例如当串口收到了数据,产生了一个中断,为了让程序更友好,休眠的进程应该能够被信号唤醒,这在资源不可获得,但却想要撤销休眠时,是比较有用的,通常也是一种比较推荐的方法。另外,我们也可以指定进程的最长休眠时间,超时后进程自动苏醒。
从上面的描述中我们可以发现,要实现阻塞操作,最重要的数据结构就是等待队列。等待队列头的数据类型是wait_queue_head_t,队列节点的数据类型是 wait_queue_t,围绕等待队列有很多宏和函数,下面罗列其最常用的一部分。
DECLARE_WAIT_QUEUE_HEAD(name)
init_waitqueue_head(q)
wait_event(wq,condition)
wait_event_timeout(wq, condition, timeout)
wake_up(x)
wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wg, condition, timeout)
wait_event_interruptible_exclusive(wq, condition)
wake_up_interruptible(x)
wake_up(x)
wait_event_interruptible_locked(wq, condition)
wait_event_interruptible_locked_irg(wq, condition)
wait_event_interruptible_exclusive_locked(wq, condition)
wait_event_interruptible_exclusive_locked_irq(wq, condition)
wake_up_locked(x)
DECLARE_WAIT_QUEUE_HEAD静态定义了一个等待队列头,init_waitqueue_head用于初始化一个等待队列头。wait_event 是在条件 condition 不成立的情况下将当前进程放入到等待队列并休眠的基本操作。它拥有非常多的变体,timeout 表示有超时限制;interruptible 表示进程在休眠时可以通过信号来唤醒;exclusive 表示该进程具有排他性,在默认情况下,唤醒操作将唤醒等待队列中的所有进程,但是如果一个进程是以排他的方式休眠的,那么唤醒操作在唤醒这个进程后不会继续唤醒其他进程;locked 要求在调用前先获得等待队列内部的锁(关于锁的机制将会在后面详细介绍),irq 要求在上锁的同时禁止中断(关于中断也将会在后面的章节详细介绍)。这些函数如果不带timeout,那么返回0表示被成功唤醒,返回-ERESTARTSYS (信号量获取失败)表示被信号唤醒;如果带timeout,返回0表示超时,返回大于0 的值表示被成功唤醒,这个值是离超时还剩余的时间。它们的唤醒函数有对应关系,简单地讲,带 locked 的用 wake_up_locked 唤醒;不带locked 而带 interruptible 的,用 wake_up_interruptible 来唤醒,否则用 wake_up 唤醒,也可以用 wake_up 唤醒。关于这些宏或函数更多的信息请参考“include/linux/wait.h”。下面是增加了阻塞操作的驱动代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/ipmi_smi.h>
#include <linux/errno.h>
#include "vser.h"
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"
struct vser_dev {
unsigned int baud;
struct option opt;
struct cdev cdev;
wait_queue_head_t rwqh;
wait_queue_head_t wwqh;
};
DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;
static int vser_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int vser_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{
int ret;
unsigned int copied = 0;
if (kfifo_is_empty(&vsfifo)){
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if(wait_event_interruptible(vsdev.rwqh, !kfifo_is_empty(&vsfifo)))
return -ERESTARTSYS;
}
ret = kfifo_to_user(&vsfifo, buf, count, &copied);
if(kfifo_is_full(&vsfifo))
wake_up_interruptible(&vsdev.wwqh);
return ret == 0 ? copied : ret;
}
static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
int ret;
unsigned int copied = 0;
if (kfifo_is_full(&vsfifo)){
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if(wait_event_interruptible(vsdev.rwqh, !kfifo_is_empty(&vsfifo)))
return -ERESTARTSYS;
}
ret = kfifo_from_user(&vsfifo, buf, count, &copied);
if(kfifo_is_empty(&vsfifo))
wake_up_interruptible(&vsdev.rwqh);
return ret == 0 ? copied : ret;
}
static long vser_ioctl(struct file *flip, unsigned int cmd, unsigned long arg)
{
if(_IOC_TYPE(cmd) != VS_MAGIC)
return -ENOTTY;
switch (cmd) {
case VS_SET_BAUD:
vsdev.baud = arg;
break;
case VS_GET_BAUD:
arg = vsdev.baud;
break;
case VS_SET_FFMT:
if(copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
return -EFAULT;
break;
case VS_GET_FFMT:
if(copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))
return -EFAULT;
break;
default:
return -ENOTTY;
}
return 0;
}
static int dat_show(struct seq_file *m, void *v)
{
struct vser_dev *dev = m->private;
seq_printf(m, "baudrate: %d\n", dev->baud);
return seq_printf(m, "frame format: %d%c%d\n", dev->opt.datab, dev->opt.parity == 0 ? 'N' : dev->opt.parity == 1 ? 'O' : 'E',dev->opt.stopb);
}
static int proc_open(struct inode *inode, struct file *file)
{
return single_open(file, dat_show, PDE_DATA(inode));
}
#if 0
static struct file_operations proc_ops = {
.owner = THIS_MODULE,
.open = proc_open,
.release = single_release,
.read = seq_read,
.llseek = seq_lseek,
};
#else
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
.unlocked_ioctl = vser_ioctl,
};
#endif
static int __init vser_init(void)
{
int ret;
dev_t dev;
init_waitqueue_head(&vsdev.rwqh);
init_waitqueue_head(&vsdev.wwqh);
dev = MKDEV(VSER_MAJOR,VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if(ret)
goto reg_err;
cdev_init(&vsdev.cdev, &vser_ops);
vsdev.cdev.owner = THIS_MODULE;
vsdev.baud = 115200;
vsdev.opt.datab = 8;
vsdev.opt.parity = 0;
vsdev.opt.stopb = 1;
ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);
if (ret)
goto add_err;
return 0;
add_err:
unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:
return ret;
}
static void __exit vser_exit(void)
{
dev_t dev;
dev = MKDEV(VSER_MAJOR,VSER_MINOR);
cdev_del(&vsdev.cdev);
unregister_chrdev_region(dev, VSER_DEV_CNT);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <E-mail>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");
阻塞在 这了
在上面的测试过程中,首先用 cat命令去读设备,因为此时 FIFO为空,所以进程阻塞,按“Ctl+C”组合键后,向程序发信号,程序退出,之后又用 echo 命令向设备写入大于32个字节的数据,当已经写了32个字节后,因为FIFO已满,所以程序被阻塞,按“Ctl+C”组合键后程序退出;之后再用 cat 命令读取数据,把32个数据读出,导致 FIFO为空,继续尝试读取,程序又阻塞,按“Ctrl+C”组合键后程序退出:接下来让 cat 在后台执行,每次echo后,cat 被唤醒,并打印读出的数据,要结束 cat时,用 kill 杀死进程最后后台运行3个echo,数据都超过32个字节,那么这3个echo 都会被阻塞,用 ps 命令查看,然后用 cat 命令读取数据,3个 echo 都被唤醒并且其写入的数据都被读出,再按“Ctrl+C”组合键结束 cat,用 ps 命令查看,发现刚才
的3个echo进程没有了。
在驱动中,如果将 wait_event_interruptible 换成 wait_event_interruptible_exclusive,并写一个测试程序,比如一次最多只读取 32 个字节,然后像上面的例子一样同时运行 3 个echo,会发现 test 程序运行只会唤醒其中的一个进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include "vser.h"
int main(int argc, char *argv[])
{
int fd;
int ret;
unsigned int baud;
struct option opt = {8,1,1};
char rbuf[32] = {0};
fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);
if (fd == -1)
goto fail;
ret = read(fd, rbuf, sizeof(rbuf));
if (ret < 0)
perror("read");
close(fd);
exit(EXIT_SUCCESS);
fail:
perror("ioctl test");
exit(EXIT_FAILURE);
}
需要说明的是,wait_event_interruptible_locked 及其变体在某些情况下非常有用,因为驱动开发者可以使用队列自带的自旋锁,从而避免一些竞态的产生,这会在后面的详细讨论。另外,有时为了对等待队列的操作实现更精确的控制(比如在复杂的锁使用下),或者根据驱动的实际情况达到更高的效率,会手动来操作等待队列,即把wait_event 或其变体的宏里面的语句提取出来,直接写在驱动中,而不用这些宏。不过万变不离其宗,根本性的工作还是要构造并初始化等待队列头,构造等待队列节点,设置进程的状态,将节点加入到等待队列,放弃 CPU,调度其他进程执行,在资源可用时,唤醒队列上的进程。