Linux驱动开发——高级I/O操作(二)

news2024/9/28 5:32:58

目录

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,调度其他进程执行,在资源可用时,唤醒队列上的进程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/429963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

公司招人,面试了50+的候选人,技术实在是太烂了····

前两个月&#xff0c;公司测试岗位面了 50候选人&#xff0c;面试下来发现几类过不了的情况&#xff0c;分享大家防止踩坑&#xff1a; 技术倒是掌握得挺多&#xff0c;但只是皮毛&#xff0c;基础知识却是一塌糊涂。工作多年&#xff0c;从未学习过工作之外的技术栈&#xff…

ERTEC200P-2 PROFINET设备完全开发手册(7-1)

7. 配置模块及自定义模块 7.1.1 PN设备的基本模型 初次接触PN的开发者&#xff0c;最容易出现的错误就是设备的实际配置与TIA的组态不一致。为了开发的过程更加顺利&#xff0c;非常有必要掌握PN设备的基础模型。PN设备的基本模型如下图描述&#xff1a; PN设备的基本构成是插…

No.039<软考>《(高项)备考大全》【第23章】综合测试管理

【第23章】综合测试管理1 章节相关1.1 考试相关1.2 案例相关2 测试监控3 测试风险管理4 测试人员绩效考核4.1 测试分类测试类型分类执行方式分类开发阶段分类5 开发测试分类参考答案1 章节相关 1.1 考试相关 必考1分选择&#xff0c;案例概率低。 1.2 案例相关 2020年下半年…

关于IOS系统时间格式显示NAN问题以及小程序项目运行报错app.json找不到

目录 问题一&#xff1a;关于IOS系统时间格式显示NAN 一、比较常见的情况&#xff0c;时间格式为"yyyy-MM-dd HH:mm:ss"格式在 iOS 会出现 NAN 二、关于时间临界值&#xff1a;对于00:00:00和24:00:00这两个时间临界值, ios会转成NAN 三、时间格式为2022/09&#…

Java语法理论和面经杂疑篇《六.泛型(Generic)》

1. 泛型概述 1.2 泛型的引入 在Java中&#xff0c;我们在声明方法时&#xff0c;当在完成方法功能时如果有未知的数据需要参与&#xff0c;这些未知的数据需要在调用方法时才能确定&#xff0c;那么我们把这样的数据通过形参表示。在方法体中&#xff0c;用这个形参名来代表那…

Medical X-rays Dataset汇总(长期更新)

目录​​​​​​​ ChestX-ray8 ChestX-ray14 VinDr-CXR VinDr-PCXR ChestX-ray8 ChestX-ray8 is a medical imaging dataset which comprises 108,948 frontal-view X-ray images of 32,717 (collected from the year of 1992 to 2015) unique patients with the text-mi…

Nginx(下载安装、常用命令、反向代理、负载均衡)

官网&#xff1a;https://nginx.org/Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器,其特点是占有内存少&#xff0c;并发能力强。下载和安装下载在Nginx的官网的下载页面中(http://nginx.org/en/download.html)&#xff…

01、Cadence使用记录之新建工程与基础操作(原理图绘制:OrCAD Capture CIS)

01、Cadence使用记录之新建工程与基础操作&#xff08;原理图绘制&#xff1a;OrCAD Capture CIS&#xff09; 硕士学电磁场去了&#xff0c;写点博客记录下学习过程。 参考的教程是B站的视频&#xff1a;allegro软件入门视频教程全集100讲 本科的时候就对Cadence有所耳闻&am…

网络安全漏洞分析与漏洞复现

前言 4 月 6 日和 5 月 18 日&#xff0c;VMware 官方发布的两则安全公告中显示&#xff0c;关乎旗下产品的 CVE 漏洞多达 10 个&#xff0c;其中不乏有 CVSSv3 评分 9.8 的高危漏洞&#xff01;如此高频的出洞速率&#xff0c;吸引了笔者注意。笔者将对 CVE-2022-22954 VMwar…

用CTGAN生成真实世界的表格数据

随着CLIP和稳定模型的快速发展&#xff0c;图像生成领域中GAN已经不常见了&#xff0c;但是在表格数据中GAN还是可以看到它的身影。 现实世界的复杂性与许多方面相关(例如&#xff0c;缺失数据、不平衡数据、噪声数据)&#xff0c;但最常见的一个问题是包含异构(或“混合”)数…

软考电子商务设计师如何备考?

关于软考电子商务设计师考什么?如何备考&#xff1f; 一、电子商务设计师概述&#xff1f; 电子商务设计师属于软考中级资格考试&#xff0c;软考是由国家人力资源和社会保障部&#xff08;原人事部&#xff09;、工业和信息化部&#xff08;原信息产业部&#xff09;领导的…

使用Softing edgePlug软件扩展数控机床的连接性

那些使用SINUMERIK 840D控制器来运行数控机床的制造商正面临着一个挑战——从车间提取机床性能和过程数据来进行分析。这些数据对于优化流程至关重要&#xff0c;但它们却无法通过传统方式来被获取。对此&#xff0c;制造商的应对方法是通过自定义代码来实现数据访问&#xff0…

Redis与MySQL的双写一致性问题

Redis与MySQL的双写一致性问题更新缓存&#xff1f; 删除缓存&#xff1f;先更新缓存再更新数据库先更新数据库&#xff0c;再更新缓存先删除缓存再更新数据库先更新数据库&#xff0c;再删除缓存解决方案1. 重试2. 异步重试2.1 使用消息队列实现重试2.2 Binlog实现异步重试删除…

10万字智慧政务大数据治理平台解决方案(word)

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除。 1 项目整体建设规划方案 按照《省人民政府关于推进数字政府建设的指导意见》(鄂政发(2019) 4号&#xff09;的规则要求&#xff0c;结合XX市“互联网政府服务”建设现状&…

【Android笔记93】Android小案例(三)之模仿小米商城(首页商品展示界面)

这篇文章,主要介绍Android小案例(三)之模仿小米商城(首页商品展示界面)。 一、模仿小米商城(首页布局) 1.1、首页运行效果 这篇文章实现的首页布局界面如下所示: 1.2、实现思路 首页轮播图,这里采用一个Banner组件实现,不知道的可以看下我之前写的一篇文章【【And…

DUET详解草稿

详解VLN动机&#xff1a;流程拓扑图Text EncoderCoarse-scale Cross-modal EncoderNode embeddingGraph-aware cross-modal encodingGlobal action predictionFine-scale Cross-modal EncoderVisual EmbeddingFine-grained cross-modal reasoningLocal action prediction and o…

heic格式怎么改成jpg?

你想知道heic格式怎么改成jpg吗&#xff1f;当我们面对heic格式图片时&#xff0c;很有可能就会遇到无法打开图片的情况。因为heic与JPG相比&#xff0c;heic格式占用空间更少&#xff0c;图像质量更无损。HEIC格式照片支持iOS11和macOS High Sierra(10.13)及更高版本。但是这种…

【数据结构】链表 详解

我们不废话&#xff0c;直入正题。引入什么是链表&#xff1f;来看看百度怎么说&#xff1a;链表是一种物理存储单元上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点&#xff08;链表中每一个元素称为结点&#…

STM32+ESP8266点灯(STA 模式)点灯(2)

1、简介 STM32ESP8266点灯&#xff08;APSTA 模式&#xff09;点灯&#xff08;1&#xff09;一文已经通过串口助手实现与网络调试助手透传&#xff0c;本文通过STM32单片机替代网络调试助手&#xff0c;实现远程点灯。 2、单片机配置 2.1 cubemax配置 2.1.1 RCC配置 2.1.2…

华为手表开发:WATCH 3 Pro(13)websocket 请求数据到服务器

华为手表开发&#xff1a;WATCH 3 Pro&#xff08;13&#xff09;websocket 请求数据到服务器初环境与设备文件夹&#xff1a;文件重点核心代码&#xff1a;app.js新增一个文本输入框index.hmlindex.cssindex.js初 希望能写一些简单的教程和案例分享给需要的人 鸿蒙可穿戴开发…