/************************ poll()函数典型模板************************/static unsigned int xxx_poll(struct file *filp, poll_table *wait){unsigned int mask = 0;struct xxx_dev *dev = filp->private_data; /*获得设备结构体指针*/...poll_wait(filp, &dev->r_wait, wait);/* 加读等待队列头 */poll_wait(filp, &dev->w_wait, wait);/* 加写等待队列头 */if (...) /* 可读 */mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/if (...) /* 可写 */mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/...return mask;}图解:
/****************************************顺序锁(seqlock)******************************************************/
/*
*注意:当要保护的资源很小,很简单,会频繁被访问而且写入访问很少发生且必须快速时,就可以使用seqlock,从本质上讲,seqlock会允许读取者对资源的自由访问,但需要读取者检查是否和写入者发生冲突,当发生这种冲突,就需要重试对资源的访问,seqlock通常不能保护包含指针的数据结构
*/
#include <linux/seqlock.h>
seqlock_t lock=SEQLOCK_UNLOCKED;
或
seqlock_t lock;
seqlock_init(&lock);
//读存取通过在进入临界区入口获取一个(无符号的)整数序列来工作. 在退出时,那个序列值与当前值比较; 如果不匹配, 读存取必须重试. 读者代码如下面的形式:
unsigned int seq;
do {
seq = read_seqbegin(&the_lock);
/* Do what you need to do */
} while read_seqretry(&the_lock, seq);
//这个类型的锁常常用在保护某种简单计算, 需要多个一致的值. 如果这个计算最后的测试表明发生了一个并发的写, 结果被简单地丢弃并且重新计算.如果你的 seqlock 可能从一个中断处理里存取, 你应当使用 IRQ 安全的版本来代替:
unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned longflags);
int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsignedlong flags);
//写者必须获取一个锁来进入由一个seqlock 保护的临界区. 为此, 调用:
void write_seqlock(seqlock_t *lock);
//写锁由一个自旋锁实现, 因此所有的通常的限制都适用. 调用:
void write_sequnlock(seqlock_t *lock);
//来释放锁. 因为自旋锁用来控制写存取, 所有通常的变体都可用:
void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
void write_seqlock_irq(seqlock_t *lock);
void write_seqlock_bh(seqlock_t *lock);
void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
void write_sequnlock_irq(seqlock_t *lock);
void write_sequnlock_bh(seqlock_t *lock);
//还有一个 write_tryseqlock 在它能够获得锁时返回非零.
/****************************end******************************************/
/***************************ioctl******************************************/
//在用户空间, ioctl 系统调用有下面的原型:
int ioctl(int fd, unsigned long cmd, ...);
//ioctl 驱动方法有和用户空间版本不同的原型:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd,unsigned long arg);
inode和filp两个指针的值对应于应用程序传递的文件描述符fd,参数cmd由用户空间不经修改地传递给驱动程序
。。。。。。
/****************************end*********************************************/
/****************************休眠*********************************************/
/*
当一个进程被置为休眠时,它会被标记为一种特殊状态并从调度器的运行队列移走,直到某些情况下修改了这个状态,进程才会在任CPU调度,也即运行该进程,休眠中的进程会被搁置在一边,等待将来的每个事件发生,我们的驱动程序不能再拥有自旋锁锁时休眠、seqlock或者RCU锁时休眠,如果我们已经禁止了中断也不能休眠,拥有信号量休眠是合法的,但任何拥有信号量
而休眠的代码必须很短,并且还要确保有能唤醒自己,能够找到休眠的进程意味着需要维护一个称为等待队列的数据结构,等待队列就是一个进程链表,其中包含了等待某个特定事件的所有进程,在linux中,一个等待队列通过一个“等待队列头”来管理
*/
//等待队列:
#include <linux/wait.h>
//初始化一个等待队列头:
DECLARE_WAIT_QUEUE_HEAD(name);
或
wait_queue_head_t xxx_queue; //定义一个等待队列头
init_waitqueue_head(&xxx_queue);//初始化对待队列头
DECLARE_WAITQUEUE(name, tsk); //定义等待队列,wait_queue_t name,struct task_struct *tsk
//添加或移除等待队列:
void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait );
//将等待队列wait添加到等待队列头q指向的等待队列链表中
void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait );
//将等待队列wait从等待队列头q指向的等待队列链表中移除
//等待事件(简单休眠):
wait_event(xxx_queue,condition);
//xxx_queue为等待队列头,是值传递,condition是任意一个布尔表达式,在条件为真前,进程会保持休眠
wait_event_interruptible(xxx_queue,condition);
//可中断,返回值为非0表示休眠被某个信号中断,而驱动程序要返回-ERESTARTSYS
wait_event_timeout(xxx_queue,condition,timeout);
//只会等待给定的时间,当给定的时间到期时,这两个宏都会返回0值,不论condition如何求值,如果由其他事件唤醒,则返回剩余的延时时间
wait_event_interruptible_timeout(xxx_queue,condition,timeout);
//唤醒休眠的进程:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
//在等待队列上睡眠:
sleep_on(wait_queue_head_t *q);
//将目前进程的状态设置为TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头,直到资源可获取,q引导的等待队列被唤醒,其和wake_up成对使用
interruptible_sleep_on(wait_queue_head_t *q);
//与上类似,其将目前进程的状态设置为TASK_INTERRUPTIBLE,其和wake_up_interruptible成对使用
/*
高级休眠:
复杂的锁定以及性能需求会强制驱动程序使用底层的函数来实现休眠
将进程置于休眠的步骤:
(1) 分配并初始化一个等待队列wait_queue_t,然后将其加入到对应的等待队列头指向的等待链表
(2) 设置进程的状态,将其标记休眠,<linux/sched.h>中定义了多个任务状态,TASK_RUNNING表示进程可运行,尽管进程并不一定在任何给定时间都运行在每个处理器上,TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE表明进程处于休眠状态,可调用下面函数来设置进程状态:
void set_current_state(int new_state); //在老代码中还可以看到current->state=TASK_INTERRUPTIBLE
(3)通过改变当前进程状态,我们只是改变了调度器处理该进程的方式,但尚未使进程让出处理器,那么最后一步就是放弃处理器
调用下面函数:
schedule(); //对schedule的调用将调用调度器,并让出CPU
*/
//手工休眠:
(1)建立并初始化一个等待队列入口
DEFINE_WAIT(xxx_wait);//name为等待队列入口变量的名称
或
wait_queue_t xxx_wait;
init_wait(&xxx_wait);
(2)将等待队列入口添加到队列中,并设置进程的状态
void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state); //queue和wait分别是等待队列头和进程入口,state为进程的新状态,它应该是TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE
(3)让出CPU
if(条件没有就绪)
schedule();
(4)一旦 schedule返回,就到了清理时间了
void finish_wait(wait_queue_head_t *queue,wait_queue_t *wait) ;
(5)之后,代码可测试其状态,并判断是否需要重新等待
//在许多设备驱动中亲自进行进程的状态改变和切换:
{
DECLARE_WAITQUEUE(wait, current); //定义等待队列
add_wait_queue(&xxx_wait,&wait ); //添加等待队列
do{
.....
if(资源未就绪)
{
__set_current_state(TASK_INTERRUPTIBLE); //改变进程状态
schedule();//调度其他进程执行
}
.....
}while(资源未就绪);
...........
...........
}
/*********************************end**********************************************/
/********************************poll机制(非阻塞)************************************/
原型:unsigned int xxx_poll(struct file filp, poll_table *wait);
// poll_table结构用于实现poll、select系统调用
#include <linux/poll.h>
(1)void poll_wait(struct file *filp,wait_queue_head_t queue, poll_table *wait);
//向poll_table结构添加一个等待队列,不会休眠,而是会在系统调用知道超时时间时,超时才会休眠
(2)poll的第二项任务是返回描述哪个操作可以立即执行的位掩码
常用的位掩码有:
POLLLIN //如果设备可以无阻塞的读取,就设置该位
POLLRDNORM
//如果通常的数据已经就绪,可以读取,就设置该位一个可读设备返回(POLLLIN|POLLRDNORM)
POLLHUP
// 当读取设备的进程到达文件尾时,驱动程序必须设置该(挂起)位,依照select功能描述,调用select的进程会被告知设备是可读的
POLLERR //设备发生了错误
POLLOUT //如果设备可以无阻塞的写入,就设置该位
POLLWRNORM
//如果通常的数据已经就绪,可以写入,就设置该位一个可读设备返回(POLLOUT|POLLWRNORM)
一般程序设计模板:
unsigned int xxx_poll(struct file filp, poll_table *wait)
{
unsigned int mask;
wait_poll(filp,&xxx_dev->inq,wait);
wait_poll(filp,&xxx_dev->outq,wait);
if(可读)
mask=POLLLIN|POLLRDNORM;
if(可写)
mask=POLLOUT|POLLWRNORM;
if(无数据可获取)
mask=POLLHUP;
return mask;
}
/**********************************end*****************************************/
【主程序1】char_dev.c
// 模块头文件
#include <linux/init.h>
#include <linux/module.h>
// 字符设备头文件
#include <linux/cdev.h>
#include <linux/fs.h>
// copy_to_user/copy_from_user
#include <linux/uaccess.h>
// ioctl
#include <linux/ioctl.h>
// 导出设备信息到sysfs
#include <linux/device.h>
#include "char_dev.h"
#include "fifo.h"
// 1. 创建字符设备对象(定义结构体变量)
// struct cdev char_demo; 静态定义
struct cdev *char_demo;
struct class * char_demo_class;
struct device *char_demo_device;
FIFO fifo; // 1. 定义
// 0-表示需用动态分配设备号 非0 - 表示静态分配设备号
int major = 0;
int minor = 0;
int char_open(struct inode *inode, struct file *filp)
{
printk("char open\n");
return 0;
}
// int read(fd, buf, sizeof(buf)); app
ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
char fifo_buf[50];
// 3. 读功能实现
ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));
if (ret < 0) {
ret = 0;
goto exit;
}
/*
* @brief 拷贝数据到应用程序空间
* @param[out] to 应用程序空间buf,要拷贝到的地方
* @param[in] from 内核空间buf,从哪个地方拷贝数据
* @param[in] n 拷贝数据长度
* @return 未成功拷贝的数据数量, 一般返回0
* @notes copy_to_user可能会引起睡眠
*/
// unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
ret = copy_to_user(buf, fifo_buf, 50);
if (ret) {
ret = -ENOMEM;
} else {
ret = 50;
}
exit:
return ret;
}
// int write(fd, buf, len);
ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
char fifo_buf[50];
if (size > 50) {
size = 50;
}
/*
* @brief 拷贝数据从应用程序空间
* @param[out] to 内核空间buf,要拷贝到的地方
* @param[in] from 应用空间buf,从哪个地方拷贝数据
* @param[in] n 拷贝数据长度
* @return 未成功拷贝的数据数量, 一般返回0
* @copy_from_user可能会引起睡眠
*/
// unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
ret = copy_from_user(fifo_buf, buf, size);
if (ret) {
ret = -ENOMEM;
} else {
ret = size;
printk("%s\n", fifo_buf);
}
// 4. 写功能实现
fifo_write(&fifo, fifo_buf);
return ret;
}
long char_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
long ret = 0;
switch (cmd) {
case CMD_DEMO0:
printk("CMD_DEMO0\n");
break;
case CMD_DEMO1:
printk("CMD_DEMO1\n");
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}
int char_release(struct inode *inode, struct file *filp)
{
printk("char release\n");
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE, // 当前模块
.open = char_open,
.read = char_read,
.write = char_write,
.unlocked_ioctl = char_ioctl,
.release = char_release,
};
int __init char_init(void)
{
int ret = 0;
dev_t devno;
// 给设备分配位置
// 设备号申请成功(cat /proc/devices 会有主设备号和名字的对应关系)
if (major != 0) {
// 静态分配设备号
// 找设备号的方法:Documention/devices
devno = MKDEV(major, 0);
ret = register_chrdev_region(devno, 1, "char demo");
if (ret) {
goto register_chrdev_region_err;
}
} else {
/*
* @brief 动态分配设备号
* @param[out] dev 返回分配的设备号
* @param[in] baseminor 第一个次设备号
* @param[in] count 分配设备号的数量
* @param[in] name 主设备号的名字
* @return 0 分配成功
* < 0 错误码
*
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
*/
ret = alloc_chrdev_region(&devno, minor, 1, "char demo");
if (ret) {
goto register_chrdev_region_err;
}
major = MAJOR(devno);
}
// 1. 创建字符设备对象(动态分配)
char_demo = cdev_alloc();
if (NULL == char_demo) {
ret = -ENOMEM;
goto cdev_alloc_err;
}
// 2. 初始化字符设备对象
char_demo->owner = THIS_MODULE;
char_demo->ops = &fops;
// cdev_init(&char_demo, &fops);
// 2. fifo初始化
fifo_init(&fifo);
// 3. 添加字符设备到内核
// ret = cdev_add(&char_demo, devno, 1);
ret = cdev_add(char_demo, devno, 1);
if (ret) {
goto cdev_add_err;
}
// 自动创建设备文件结点(udevfs/mdev)
// 1. 创建设备类
char_demo_class = class_create(THIS_MODULE, "char_demo");
if (IS_ERR(char_demo_class)) {
ret = PTR_ERR(char_demo_class);
goto class_create_err;
}
// 2. 导出设备信息到应用空间(类的目录下)
char_demo_device = device_create(char_demo_class, NULL, devno, NULL, "chardev");
if (IS_ERR(char_demo_device)) {
ret = PTR_ERR(char_demo_class);
goto device_create_err;
}
goto register_chrdev_region_err;
device_create_err:
class_destroy(char_demo_class);
class_create_err:
cdev_del(char_demo);
cdev_add_err:
cdev_alloc_err:
unregister_chrdev_region(devno, 1);
register_chrdev_region_err:
return ret;
}
void __exit char_exit(void)
{
dev_t devno = MKDEV(major, minor);
device_destroy(char_demo_class, devno);
class_destroy(char_demo_class);
// cdev_del(&char_demo);
cdev_del(char_demo);
unregister_chrdev_region(devno, 1);
}
module_init(char_init);
module_exit(char_exit);
MODULE_LICENSE("GPL");
【主程序2】fifo.c
#include "fifo.h"
void fifo_init(FIFO *fifo)
{
memset(fifo->buf, 0, sizeof(fifo->buf));
fifo->read = 0;
fifo->write = 0;
// 2. 自旋锁初始化
spin_lock_init(&fifo->lock);
}
int fifo_read(FIFO *fifo, char *buf, int size)
{
if (NULL == fifo) {
return -1;
}
if (size > ENUM_SIZE) {
return -1;
}
spin_lock(&fifo->lock);
if (fifo->read == fifo->write) {
spin_unlock(&fifo->lock);
return -1;
}
spin_unlock(&fifo->lock);
memcpy(buf, fifo->buf[fifo->read], ENUM_SIZE);
spin_lock(&fifo->lock);
fifo->read += 1;
fifo->read %= BUF_NUM;
spin_unlock(&fifo->lock);
return 0;
}
int fifo_write(FIFO *fifo, char *buf)
{
if (NULL == fifo) {
return -1;
}
if (NULL == buf) {
return -1;
}
spin_lock(&fifo->lock);
if ((fifo->write + 1) % ENUM_SIZE == fifo->read) {
spin_unlock(&fifo->lock);
return -1;
}
spin_unlock(&fifo->lock);
memcpy(fifo->buf[fifo->write], buf, ENUM_SIZE);
spin_lock(&fifo->lock);
fifo->write += 1;
fifo->write %= BUF_NUM;
spin_unlock(&fifo->lock);
return 0;
}
【头文件1】char_dev.h
#ifndef __CHAR_DEV_H__
#define __CHAR_DEV_H__
#define CMD_DEMO0 _IO('K', 0)
#define CMD_DEMO1 _IO('K', 1)
#endif // __CHAR_DEV_H__
【头文件2】fifo.h
#ifndef __FIFO_H__
#define __FIFO_H__
#include <linux/spinlock.h>
#include <linux/string.h>
#define BUF_NUM 5
#define ENUM_SIZE 50
typedef struct fifo_t{
char buf[BUF_NUM][ENUM_SIZE];
int read, write;
// 自旋锁使用 1. 定义
spinlock_t lock;
} FIFO;
void fifo_init(FIFO *fifo);
int fifo_read(FIFO *fifo, char *buf, int size);
int fifo_write(FIFO *fifo, char *buf);
#endif // __FIFO_H__
【Makefile】
# KERNELRELEASE 变量装载的是内核版本,变量在内核顶层目录下的Makefile赋值
ifeq ($(KERNELRELEASE),)
# 内核源代码目录(模块将运行的内核的)
#KERNELDIR ?= /usr/src/linux-headers-$(shell uname -r) #(uname -r查看)
KERNELDIR ?= /work/S5PC100/linux-2.6.35-farsight
PWD := $(shell pwd)
# 调用内核顶层目录下的Makefile,是它调用本Makefile编译本模块
# -C 在做任何事情之前,进入指定目录
# M= 告诉内核顶层目录的Makefile,模块的位置
# modules 告诉内核顶层目录的Makefile 仅仅编译模块
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
# 清除编译出来的文件
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules*
else
# 被内核顶层目录的Makefile调用,来编译模块
obj-m += char_fifo.o #模块重命名
char_fifo-objs := char_dev.o fifo.o
endif
相对于上面的自旋锁,只需要修改主程序1中的几个函数:
// int read(fd, buf, sizeof(buf)); app
ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
char fifo_buf[50];
wait_event_interruptible(readq, fifo_have_data(&fifo));
// 3. 读功能实现
ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));
if (ret < 0) {
ret = 0;
goto exit;
}
ret = copy_to_user(buf, fifo_buf, 50);
if (ret) {
ret = -ENOMEM;
} else {
ret = 50;
}
exit:
return ret;
}
ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
char fifo_buf[50];
if (size > 50) {
size = 50;
}
ret = copy_from_user(fifo_buf, buf, size);
if (ret) {
ret = -ENOMEM;
} else {
ret = size;
printk("%s\n", fifo_buf);
}
// 4. 写功能实现
fifo_write(&fifo, fifo_buf);
// 4. 另一进程唤醒
wake_up_interruptible(&readq);
return ret;
}
相对于上面的阻塞,只需要修改主程序1中的几个函数:
int char_open(struct inode *inode, struct file *filp)
{
if (filp->f_flags & O_NONBLOCK) {
printk("no block\n");
}
printk("char open\n");
return 0;
}
ssize_t char_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
char fifo_buf[50];
// 5. 实现读非阻塞
if (filp->f_flags & O_NONBLOCK) {
// 非阻塞实现
if (!fifo_have_data(&fifo)) {
return -EAGAIN;
}
} else {
wait_event_interruptible(readq, fifo_have_data(&fifo));
}
ret = fifo_read(&fifo, fifo_buf, sizeof(fifo_buf));
if (ret < 0) {
ret = 0;
goto exit;
}
ret = copy_to_user(buf, fifo_buf, 50);
if (ret) {
ret = -ENOMEM;
} else {
ret = 50;
}
exit:
return ret;
}
ssize_t char_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
char fifo_buf[50];
if (size > 50) {
size = 50;
}
ret = copy_from_user(fifo_buf, buf, size);
if (ret) {
ret = -ENOMEM;
} else {
ret = size;
printk("%s\n", fifo_buf);
}
fifo_write(&fifo, fifo_buf);
// 4. 另一进程唤醒
wake_up_interruptible(&readq);
return ret;
}
注:以上总结部分选自韦东山群答疑助手:沈朝平《Linux驱动程序学习笔记》!非常感谢!