linux驱动学习笔记---实现中断下半部以及驱动编写规范(七)

发布者:EnigmaticSoul最新更新时间:2025-02-18 来源: cnblogs关键字:linux驱动  中断  驱动 手机看文章 扫描二维码
随时随地手机看文章

中断下半部:
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 errorn');
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 errorn');
return -ENOMEM;
}


// 1, 申请主设备号--动态申请主设备号
key_dev->major = register_chrdev(0, 'key_dev', &key_fops);
if(key_dev->major < 0)
{
printk(KERN_ERR'register_chrdev errorn');
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 #include
//参数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{
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 errorn');
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#include

// 设计一个对象--描述按键产生的数据包
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 = %dn', major, minor);
printk('major2 = %d, minor2 = %dn', 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 errorn');
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('--%s : releasen', pdesc->name);
//填充值
key_dev->event.code = pdesc->code;
key_dev->event.value = 0;

}else
{
//按下
printk('--%s : pressedn', 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 errorn');
return -ENOMEM;
}


// 1, 申请主设备号--动态申请主设备号
key_dev->major = register_chrdev(0, 'key_dev', &key_fops);
if(key_dev->major < 0)
{
printk(KERN_ERR'register_chrdev errorn');
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 #include
//参数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{
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 errorn');
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{
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
#include
#include
#include
#include

#include

// 设计一个对象--描述按键产生的数据包
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] = '';
printf('kbd_buf = %sn', 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('---KEY_UP pressedn');
}else
{
printf('---KEY_UP releasen');
}
break;
case KEY_DOWN:
if(data.value)
{
printf('---KEY_DOWN pressedn');
}else
{
printf('---KEY_DOWN releasen');
}
break;
case KEY_LEFT:
if(data.value)
{
printf('---KEY_LEFT pressedn');
}else
{
printf('---KEY_LEFT releasen');
}
break;
case KEY_RIGHT:
if(data.value)
{
printf('---KEY_RIGHT pressedn');
}else
{
printf('---KEY_RIGHT releasen');
}
break;

}

}
}

}




close(fd);


}

[1] [2]
关键字:linux驱动  中断  驱动 引用地址:linux驱动学习笔记---实现中断下半部以及驱动编写规范(七)

上一篇:3-Uboot源码目录分析
下一篇:uboot-spl 编译流程

推荐阅读最新更新时间:2026-02-20 10:45

ARM Linux各种驱动中断服务程序工作在ARM的IRQ模式吗?
大家都知道,ARM有IRQ, FIQ, USR,SVC,ABORT等各种模式。当系统收到IRQ的时候,会进入ARM的IRQ模式。那么,ARM Linux各种驱动的中断服务程序工作在ARM的IRQ模式吗? 答案是否定的。 我们加一段汇编来读CPSR: 然后我们随便找一个ARM Linux的中断服务程序去打印CPSR: 然后我们发现打印出来的值是: cpsr:40000193 低8位的二进制是10010011 那么对应ARM CPSR的查询,可以看出CPU处于ARM的SVC模式(低5位是10011),而且I bit被设置(第7位是1),所以是禁止IRQ的。 模式表: 可见,ARM Linux最初进入IRQ模式后,比
[单片机]
ARM <font color='red'>Linux</font>各种<font color='red'>驱动</font>的<font color='red'>中断</font>服务程序工作在ARM的IRQ模式吗?
arm 驱动linux内核驱动中断下半部编程
本文部分参考华清远见文档 中断上半部要求执行时间间隔段,所以往往将处理时间较长的代码放在中断下半部来处理 中断下半部的应用:网卡驱动上半部初始化网卡驱动等短时间的事件,下半部收发数据 中断下半部: a, 下半部产生的原因: 1,中断上下文中不能阻塞,这也限制了中断上下文中能干的事 2,中断处理函数执行过程中仍有可能被其他中断打断,都希望中断处理函数执行得越快越好。 基于上面的原因,内核将整个的中断处理流程分为了上半部和下半部。上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应之后马上要做的事情。而一些需要在中断处理函数后继续执行的操作,内核建议把它放在下半部执行。 比如:在linux内核中,
[单片机]
ARM-Linux驱动--ADC驱动中断方式)
硬件平台:FL2440 内核版本:2.6.28 主机平台:Ubuntu 11.04 内核版本:2.6.39 原创作品,转载请标明出处:http://blog.csdn.net/yming0221/archive/2011/06/26/6568937.aspx 这个驱动写了好久,因为原来的Linux内核编译的时候将触摸屏驱动编译进内核了,而触摸屏驱动里的ADC中断在注册的时候类型选择的是 IRQF_SAMPLE_RANDOM,不是共享类型,所以,自己写的ADC驱动在每次open的时候,总提示ADC中断注册失败。 解决方案: 重新配置内核,选择触摸屏驱动以模块的形式编译,而不是直接编译进内核,这样Linux在启
[单片机]
ARM-<font color='red'>Linux</font><font color='red'>驱动</font>--ADC<font color='red'>驱动</font>(<font color='red'>中断</font>方式)
迅为IMX6ULL开发板Linux驱动初探-最简单的设备驱动-helloworld
经过前面的学习,我们了解了驱动开发的框架,本章节将带领大家实验操作,写最简单的驱动-helloworld。 Linux 设备驱动会以内核模块的形式出现,因为 linux 内核的整体架构就非常庞大,包含的组件也非常多,如果把所有的功能都编译到 linux 内核中会使得内核非常臃肿,为了解决这个问题,更方便地新增和删除功能,linux 提供了这样的机制,这种机制被称为模块。为了大家对模块有一个感性的认识,我们先来看一个最简单的驱动-helloworld。 驱动分为四个部分:  头文件  驱动模块的入口函数和出口函数  声明信息  功能实现 我们在 windows 上面新建一个 helloworld.c 文件,这里使用 sour
[单片机]
迅为IMX6ULL开发板<font color='red'>Linux</font><font color='red'>驱动</font>初探-最简单的设备<font color='red'>驱动</font>-helloworld
linux驱动开发(十)——misc杂散设备
1:什么是misc驱动模型? 2:为什么要有misc驱动模型? 3:misc驱动模型的代码实现 4:misc驱动模型实战 参考: http://blog.csdn.net/yicao821/article/details/6785738 http://www.thinksaas.cn/topics/0/507/507168.html http://www.cnblogs.com/fellow1988/p/6235080.html https://www.zhihu.com/question/21508904 http://www.cnblogs.com/snake-hand/p/3212483.html http://blog.c
[单片机]
linux驱动(五)内核中静态映射表的建立
1:我们在linux内核中都是开启mmu的所以都是用的虚拟地址,需要建立VA到PA的映射表; 我们内核中映射表在arch/arm/mach-s5pv210/mach-smdkc110.c文件中 建立映射的函数是,smdkc110_map_io建立映射表 smdkc110_map_io 这个函数调用s5p_init_io函数真正     s5p_init_io         iotable_init     s3c24xx_init_clocks     s5pv210_gpiolib_init     s3c24xx_init_uarts smdkc110_map_io 这个函数调用s5p_init_io函数,s5p_init
[单片机]
<font color='red'>linux</font><font color='red'>驱动</font>(五)内核中静态映射表的建立
Linux下实现流水灯等功能的LED驱动代码及测试实例
驱动代码: #include linux/errno.h #include linux/kernel.h #include linux/module.h #include linux/slab.h #include linux/input.h #include linux/init.h #include linux/serio.h #include linux/delay.h #include linux/clk.h #include linux/miscdevice.h #include linux/gpio.h #include asm/io.h #include asm/irq.h #incl
[单片机]
Linux下GPIO驱动(三) ----gpio_desc()的分析
上篇最后提出的疑问是结构体gpio_chip中的成员函数set等是怎么实现的,在回答之前先介绍下gpio_desc这个结构体。 如上图所示,右上方部分为GPIO驱动对其它驱动提供的GPIO操作接口,其对应的右下方部分为GPIO硬件操作接口,也就是说对外提供的接口最终会一一对应的对硬件GPIO进行操作。 再来看左边部分,左上方部分为一全局数组,记录各个GPIO的描述符,即对应左下方的gpio_desc结构体,其中gpio_chip指向硬件层的GPIO,flags为一标志位,用来指示当前GPIO是否已经占用,当用gpio_request申请GPIO资源时,flags位就会置位,当调用gpio_free释放GPIO
[单片机]
<font color='red'>Linux</font>下GPIO<font color='red'>驱动</font>(三) ----gpio_desc()的分析
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved