linux驱动学习笔记---实现中断下半部以及驱动编写规范(七)【转】
转自:https://blog.csdn.net/weixin_42471952/article/details/81609141
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42471952/article/details/81609141
中断下半部:
tasklet :
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long); //下半部要执行的代码
unsigned long data; // 传递给func的参数
};
1, 初始化tasklet
tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)
2, 在中断上半部,将tasklet加入到内核线程
tasklet_schedule(struct tasklet_struct * t)
3, 模块卸载的时候,需要从内核线程中移除tasklet
tasklet_kill(struct tasklet_struct * t)
------------------------------------------------------------------
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;//下半部要执行的代码
};
1, 初始化work
INIT_WORK(struct work_struct * work, work_func_t func);
work_func_t func为结构体struct work_struct中的函数指针;
2, 在中断上半部,将work加入到内核线程
schedule_work(struct work_struct * work);
3, 模块卸载的时候,需要从内核线程中移除work
cancel_work_sync(struct work_struct * work)
工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。
那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。
引用: https://blog.csdn.net/av_geek/article/details/41278801
一个为 struct tasklet_struct *next任务链表,一个为struct list_head entry;内核链表
驱动编写规范
设计一个对象描述所有的全局变量
//设计一个对象类型--描述所有的全局的变量
struct s5pv210_key{
__u8 have_data; //用于描述是否有数据
int major ; //记录主设备号
struct class *cls; //用于创建 类
struct device *dev; //用来创建设备文件
wait_queue_head_t wq_head; //用于实现阻塞的等待队列头
struct key_event event; //用于存放数据的包
struct work_struct work;//用于实现中断下半部
};
声明一个对象
//声明一个对象
struct s5pv210_key *key_dev;
初始化时统一申请空间(这也是好处之一)
// 0-为全局设备/数据对象分配空间
//参数2--标志--分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待)
key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
错误判断
if(key_dev == NULL)
{
printk(KERN_ERR"kmalloc error\n");
return -ENOMEM;
}
指针错误判断
if(IS_ERR(key_dev->cls))
{
ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码
goto err_unregister;
}
统一下函数的最后做处理
static int __init key_drv_init(void)
{
int ret;
// 0-为全局设备/数据对象分配空间
//参数2--标志--分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待)
key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
if(key_dev == NULL)
{
printk(KERN_ERR"kmalloc error\n");
return -ENOMEM;
}
// 1, 申请主设备号--动态申请主设备号
key_dev->major = register_chrdev(0, "key_dev", &key_fops);
if(key_dev->major < 0)
{
printk(KERN_ERR"register_chrdev error\n");
ret = key_dev->major;
goto err_free;
}
// 2, 创建设备节点
key_dev->cls = class_create(THIS_MODULE, "key_cls");
if(IS_ERR(key_dev->cls))
{
ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码
goto err_unregister;
}
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major, 0), NULL, "key0");
if(IS_ERR(key_dev->dev))
{
ret = PTR_ERR(key_dev->dev);
goto err_destory_cls;
}
// 3, 硬件初始化-- 映射地址或者中断申请
//参数1--中断号码
//获取中断号码的方法: 1,外部中断IRQ_EINT(x)
// 2, 头文件 #include <mach/irqs.h> #include <plat/irqs.h>
//参数2--中断的处理方法
//参数3--中断触发方式: 高电平,低电平,上升沿,下降沿,双边沿
/*
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
*/
//参数4--中断的描述-自定义字符串
//参数5--传递个参数2的任意数据
//返回值0表示正确
int i;
int irqno;
int flags;
char *name;
for(i=0; i<ARRAY_SIZE(all_keys); i++)
{
name = all_keys[i].name;
irqno = all_keys[i].irqno;
flags = all_keys[i].flags;
ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);
if(ret != 0)
{
printk("request_irq error\n");
goto err_destroy_dev;
}
}
// 定义一个等待队列头,并且初始化
init_waitqueue_head(&key_dev->wq_head);
//初始化work
INIT_WORK(&key_dev->work, work_key_irq);
return 0;
err_destroy_dev:
device_destroy(key_dev->cls, MKDEV(key_dev->major, 0));
err_destory_cls:
class_destroy(key_dev->cls);
err_unregister:
unregister_chrdev(key_dev->major, "key_dev");
err_free:
kfree(key_dev);
return ret;
}
完整驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
// 设计一个对象--描述按键产生的数据包
struct key_event{
int code; //什么按键: 左键, 回车键
int value; // 按键的状态: 按下/抬起 (1/0)
};
//设计一个描述按键的对象: 名字, irqno, gpio, 按键值,触发方式
struct key_desc{
char *name;
int irqno;
int gpio;
int code;
int flags;// 触发方式
};
//设计一个对象类型--描述所有的全局的变量
struct s5pv210_key{
__u8 have_data; //用于描述是否有数据
int major ; //记录主设备号
struct class *cls; //用于创建 类
struct device *dev; //用来创建设备文件
wait_queue_head_t wq_head; //用于实现阻塞的等待队列头
struct key_event event; //用于存放数据的包
struct work_struct work;//用于实现中断下半部
};
//声明一个对象
struct s5pv210_key *key_dev;
struct key_desc all_keys[] = {
[0] = {
.name = "key1_up_eint0",
.irqno = IRQ_EINT(0),
.gpio = S5PV210_GPH0(0),
.code = KEY_UP,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[1] = {
.name = "key2_down_eint1",
.irqno = IRQ_EINT(1),
.gpio = S5PV210_GPH0(1),
.code = KEY_DOWN,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[2] = {
.name = "key3_left_eint2",
.irqno = IRQ_EINT(2),
.gpio = S5PV210_GPH0(2),
.code = KEY_LEFT,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[3] = {
.name = "key4_right_eint3",
.irqno = IRQ_EINT(3),
.gpio = S5PV210_GPH0(3),
.code = KEY_LEFT,
.flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
};
int key_drv_open (struct inode *inode, struct file *filp)
{
printk("--------^_* %s-------\n", __FUNCTION__);
// 通过文件路径可以得到inode
// 通过得到次设备号可以区分不同的设备
//int major = MAJOR(filp->f_path.dentry->d_inode->i_rdev);
int major = imajor(filp->f_path.dentry->d_inode);
int major2 = imajor(inode);
int minor = iminor(filp->f_path.dentry->d_inode);
int minor2 = iminor(inode);
printk("major = %d, minor = %d\n", major, minor);
printk("major2 = %d, minor2 = %d\n", major2, minor2);
memset(&key_dev->event, 0, sizeof(struct key_event));
key_dev->have_data= 0; //为假--一开始都没有按键按下或者抬起
return 0;
}
ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret;
//区分当前是阻塞还是非阻塞
if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
{
return -EAGAIN;
}
// 在资源不可达的时候,进行休眠
// 参数1---当前驱动中的等待队列头
// 参数2--休眠的条件: 假的话就休眠,真就不休眠
wait_event_interruptible(key_dev->wq_head, key_dev->have_data);
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0)
{
printk("copy_to_user error\n");
return -EFAULT;
}
//清零,以备下次充值
memset(&key_dev->event, 0, sizeof(struct key_event));
key_dev->have_data = 0; //没有数据了,等待下一次数据
return count;
}
int key_drv_close(struct inode *inode, struct file *filp)
{
printk("--------^_* %s-------\n", __FUNCTION__);
return 0;
}
unsigned int key_drv_poll (struct file *filp, struct poll_table_struct *pts)
{
//返回一个整数: 没有数据的时候返回0, 有数据的时候,返回POLLIN
unsigned int mask = 0;
// 将当前的等待队列头,注册到vfs层, 注意: poll_wait该函数不会导致休眠
//参数1-文件对象--当前file
//参数2--当前的等待队列头
//参数3--当前传递过来的第三个参数
poll_wait(filp, &key_dev->wq_head, pts);
if(key_dev->have_data)
mask |= POLLIN;
return mask;
}
// 4, 实现fops
const struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_drv_open,
.read = key_drv_read,
.poll = key_drv_poll,
.release =key_drv_close,
};
void work_key_irq(struct work_struct *work)
{
printk("-----------%s-------\n", __FUNCTION__);
key_dev->have_data = 1;//表示有数据了
wake_up_interruptible(&key_dev->wq_head);
}
//中断处理方法
//参数1--当前产生的中断的号码
//参数2--request_irq传递过来的参数
irqreturn_t key_irq_handler(int irqno, void *dev_id)
{
int *p = (int *)dev_id;
//printk("-----------%s-------0x%x-----\n", __FUNCTION__, *p);
//区分当前是哪个按键
struct key_desc *pdesc = (struct key_desc *)dev_id;
//区分按下还是抬起
int value = gpio_get_value(pdesc->gpio);
if(value)
{
//抬起
printk("<kernel>--%s : release\n", pdesc->name);
//填充值
key_dev->event.code = pdesc->code;
key_dev->event.value = 0;
}else
{
//按下
printk("<kernel>--%s : pressed\n", pdesc->name);
key_dev->event.code = pdesc->code;
key_dev->event.value = 1;
}
//在中断上半部,将work加入到内核线程
schedule_work(&key_dev->work);
return IRQ_HANDLED;
}
static int __init key_drv_init(void)
{
int ret;
// 0-为全局设备/数据对象分配空间
//参数2--标志--分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待)
key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
if(key_dev == NULL)
{
printk(KERN_ERR"kmalloc error\n");
return -ENOMEM;
}
// 1, 申请主设备号--动态申请主设备号
key_dev->major = register_chrdev(0, "key_dev", &key_fops);
if(key_dev->major < 0)
{
printk(KERN_ERR"register_chrdev error\n");
ret = key_dev->major;
goto err_free;
}
// 2, 创建设备节点
key_dev->cls = class_create(THIS_MODULE, "key_cls");
if(IS_ERR(key_dev->cls))
{
ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码
goto err_unregister;
}
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major, 0), NULL, "key0");
if(IS_ERR(key_dev->dev))
{
ret = PTR_ERR(key_dev->dev);
goto err_destory_cls;
}
// 3, 硬件初始化-- 映射地址或者中断申请
//参数1--中断号码
//获取中断号码的方法: 1,外部中断IRQ_EINT(x)
// 2, 头文件 #include <mach/irqs.h> #include <plat/irqs.h>
//参数2--中断的处理方法
//参数3--中断触发方式: 高电平,低电平,上升沿,下降沿,双边沿
/*
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
*/
//参数4--中断的描述-自定义字符串
//参数5--传递个参数2的任意数据
//返回值0表示正确
int i;
int irqno;
int flags;
char *name;
for(i=0; i<ARRAY_SIZE(all_keys); i++)
{
name = all_keys[i].name;
irqno = all_keys[i].irqno;
flags = all_keys[i].flags;
ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);
if(ret != 0)
{
printk("request_irq error\n");
goto err_destroy_dev;
}
}
// 定义一个等待队列头,并且初始化
init_waitqueue_head(&key_dev->wq_head);
//初始化work
INIT_WORK(&key_dev->work, work_key_irq);
return 0;
err_destroy_dev:
device_destroy(key_dev->cls, MKDEV(key_dev->major, 0));
err_destory_cls:
class_destroy(key_dev->cls);
err_unregister:
unregister_chrdev(key_dev->major, "key_dev");
err_free:
kfree(key_dev);
return ret;
}
static void __exit key_drv_exit(void)
{
//移除work
cancel_work_sync(&key_dev->work);
// 释放中断
//参数1--中断号码
//参数5--和request_irq第5个参数保持一致
int i;
int irqno;
for(i=0; i<ARRAY_SIZE(all_keys); i++)
{
irqno = all_keys[i].irqno;
free_irq(irqno, &all_keys[i]);
}
device_destroy(key_dev->cls, MKDEV(key_dev->major, 0));
class_destroy(key_dev->cls);
unregister_chrdev(key_dev->major, "key_dev");
kfree(key_dev);
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
应用层app代码
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <poll.h>
#include <linux/input.h>
// 设计一个对象--描述按键产生的数据包
struct key_event{
int code; //什么按键: 左键, 回车键
int value; // 按键的状态: 按下/抬起 (1/0)
};
int main(int argc, char *argv[])
{
int on;
int ret;
char kbd_buf[128];
struct key_event data;
//直接将驱动模块当做文件来操作
int fd = open("/dev/key0", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
struct pollfd pfd[2];
pfd[0].fd = fd; //自己写的按键设备
pfd[0].events = POLLIN; //监控读 (POLLIN|POLLOUT|POLLERR)
pfd[1].fd = 0; //监控标准输入
pfd[1].events = POLLIN;
while(1)
{
//参数1--你需要监控的文件描述符的集合
//参数2--监控的文件的个数
//参数3--监控的时间--毫秒为单位,如果是负数,永久监控
ret = poll(pfd, 2, -1);
if(ret < 0)
{
perror("poll");
exit(1);
}
if(ret > 0)
{
//判断是哪个有数据
if(pfd[1].revents & POLLIN)
{
//表示键盘是输入
ret = read(0, kbd_buf, 128);
//fgets(kbd_buf, 128, stdin);
kbd_buf[ret] = '\0';
printf("kbd_buf = %s\n", kbd_buf);
}
if(pfd[0].revents & POLLIN)
{
//获取数据--不会阻塞
ret = read(pfd[0].fd, &data, sizeof(struct key_event));
//解析包
switch(data.code)
{
case KEY_UP:
if(data.value)
{
printf("<app>---KEY_UP pressed\n");
}else
{
printf("<app>---KEY_UP release\n");
}
break;
case KEY_DOWN:
if(data.value)
{
printf("<app>---KEY_DOWN pressed\n");
}else
{
printf("<app>---KEY_DOWN release\n");
}
break;
case KEY_LEFT:
if(data.value)
{
printf("<app>---KEY_LEFT pressed\n");
}else
{
printf("<app>---KEY_LEFT release\n");
}
break;
case KEY_RIGHT:
if(data.value)
{
printf("<app>---KEY_RIGHT pressed\n");
}else
{
printf("<app>---KEY_RIGHT release\n");
}
break;
}
}
}
}
close(fd);
}
————————————————
版权声明:本文为CSDN博主「Moonright」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42471952/article/details/81609141
linux驱动学习笔记---实现中断下半部以及驱动编写规范(七)【转】的更多相关文章
- linux 驱动学习笔记01--Linux 内核的编译
由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...
- linux kernel学习笔记-5内存管理_转
void * kmalloc(size_t size, gfp_t gfp_mask); kmalloc()第一个参数是要分配的块的大小,第一个参数为分配标志,用于控制kmalloc()的行为. km ...
- IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试
IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试 目的不是为了编写TSC2007驱动,是为了学习IIC驱动的编写,读一下TSC2007的ADC数据进行练习,, Linux主机驱动和外设驱 ...
- Linux系统学习笔记:文件I/O
Linux支持C语言中的标准I/O函数,同时它还提供了一套SUS标准的I/O库函数.和标准I/O不同,UNIX的I/O函数是不带缓冲的,即每个读写都调用内核中的一个系统调用.本篇总结UNIX的I/O并 ...
- Linux内核学习笔记-2.进程管理
原创文章,转载请注明:Linux内核学习笔记-2.进程管理) By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- Linux内核学习笔记-1.简介和入门
原创文章,转载请注明:Linux内核学习笔记-1.简介和入门 By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- Linux内核学习笔记二——进程
Linux内核学习笔记二——进程 一 进程与线程 进程就是处于执行期的程序,包含了独立地址空间,多个执行线程等资源. 线程是进程中活动的对象,每个线程都拥有独立的程序计数器.进程栈和一组进程寄存器 ...
- stm32学习笔记——外部中断的使用
stm32学习笔记——外部中断的使用 基本概念 stm32中,每一个GPIO都可以触发一个外部中断,但是,GPIO的中断是以组为一个单位的,同组间的外部中断同一时间只能使用一个.比如说,PA0,PB0 ...
- 20135316王剑桥Linux内核学习笔记
王剑桥Linux内核学习笔记 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 计算机是如何工作的 个人理 ...
随机推荐
- 【BZOJ2437】[Noi2011]兔兔与蛋蛋(博弈+二分图)
传送门 题意: 给出一个\(n*m\)的棋盘,上面有若干个黑色棋子,若干个白色棋子,还有一个空格. 每次先手选择一个空格,将它与相邻的某个白色棋子交换:后手则选择一个空格,与相邻的某个黑色棋子交换. ...
- Centos 7 LAMP+wordpress
一.简介 LAMP--->Linux(OS).Apache(http服务器),MySQL(有时也指MariaDB,数据库) 和PHP的第一个字母,一般用来建立web应用平台. 它是 ...
- 读取本地文件转化成MultipartFile
介绍 现在有个上传文件功能,需要将文件上传到oss上,但是文件有点多,于是使用接口进行上传.但是需要上传文件转换为MultipartFile类型文件进行上传. 主要代码 添加pom文件 <dep ...
- <Tree> 298 250 366 199(高频) 98(高频)
298. Binary Tree Longest Consecutive Sequence 先序遍历,根左右.如果该节点的 value == 父节点value + 1, 则长度+1; 否则重置为1. ...
- mysqltest语法整理
1. mysqltest是mysql自带的测试引擎, 它实现了一种小语言,用来描述测试过程,并将测试结果与预期对比. 小语言按照语法大致分为三类:mysql command,sql,comment.s ...
- [LOJ 6432][PKUSC 2018]真实排名
[LOJ 6432][PKUSC 2018]真实排名 题意 给定 \(n\) 个选手的成绩, 选中其中 \(k\) 个使他们的成绩翻倍. 对于每个选手回答有多少种方案使得他的排名不发生变化. \(n\ ...
- 使用Python写yaml用例
1.打开cmd,进入本机安装python的目录,执行 pip install pyyaml ,安装pyyaml第三方包. 2.在Pycharm中新建一个项目(已有的话就不需要啦) 新建yaml文件 ...
- CSP-S 2019 AFO记
DAY -1 上午并没有改出题,然而调出了动态$dp$,于是顺便$AC$了保卫王国,于是就很愉悦. 下午考前自闭赛,把会的题和原题写了出来,然后就$rank1$了,感觉自己$rp--$. 晚上发现$T ...
- Oracle 存储过程包(Package、Package Body)
初出茅庐,不知原来存储过程还可以写得如此复杂,而且还竟然可以调试! 好吧,得整理一下存储过程的一些语法,以备以后用到时可以查阅. 使用数据库:Oracle 数据库工具:PL/SQL Developer ...
- Spring框架spring-web模块中的RestTemplate类详解
RestTemplate类是spring-web模块中进行HTTP访问的REST客户端核心类.RestTemplate请求使用阻塞式IO,适合低并发的应用场景. 1. RestTemplate类提供了 ...