五:动态加载驱动
1.区别
静态编译进内核:驱动存在于ulmage
中,内核启动时加载驱动
动态加载驱动:驱动独立存在(xxx.ko
),内核启动后动态加载
动态编译只需要make modules
然后在内核insmod
,就不需要reboot重复流程,直接编译就行
2.代码
MODULE_LIENCES("GPL"); //在led4.c最后面加,符合gnu的协议,内核不会“脏”
config LED4
tristate "this is led4" //改成三态
default y
---help---
this is demo_secondTest driver
在.config
CONFIG_LED4=m
make menuconfig 中是 M 状态,改成 M
cp drivers/char/led4.ko ~/nfs/rootfs
// make modules -- M全部编译成.ko
cp arch/arm/boot/uImage ~/tftpboot/
//make uImage
内核中
insmod led4.ko //动态加载驱动模块
mknod /dev/led4 c 253 0
lsmod //查看动态加载的驱动模块
rmmod led4 //卸载动态加载的驱动模块(卸载使用模块名)
1.自带寄存器函数
#define GPIO_LED S3C2410_GPB(5) //引脚GPB5
2.自动创建设备节点次class/device
alloc_chrdev_region
class_create
device_create //创造设备节点
#include <asm/io.h>
#include <asm/ioctl.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/types.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/device.h>
#include <linux/cdev.h>
#define DEV_NAME "led4_alloc"
#define GPIO_LED S3C2410_GPB(5)
#define LED_ON 0
#define LED_OFF 1
static void led2_init(void) {
gpio_request(GPIO_LED, "led4"); //申请
gpio_direction_output(GPIO_LED, LED_OFF);
}
static void led2_on(void) { gpio_set_value(GPIO_LED, LED_ON); }
static void led2_off(void) { gpio_set_value(GPIO_LED, LED_OFF); }
static void led2_deinit(void) {
gpio_set_value(GPIO_LED, LED_OFF);
gpio_free(GPIO_LED);
}
//以下都是函数指针
static int open(struct inode *node, struct file *file) {
led2_init();
printk("led4 open ...\n"); //内核打印
return 0;
}
static ssize_t read(struct file *file, char __user *buf, size_t len,
loff_t *offset) {
// copy_to_user();
printk("led4 read ...\n");
return 0;
}
static ssize_t write(struct file *file, const char __user *buf, size_t len,
loff_t *offset) {
unsigned char data[10] = {0};
unsigned int len_cp = (sizeof(data) < len) ? sizeof(data) : len;
ssize_t ret = len_cp;
// strcmp(buf,"ledon")
// copy_to_user 用户访问
copy_from_user(
data, buf,
len_cp); //让代码不要在内核空间运行去访问,如果数据有问题,内核访问野指针,内核崩溃
if (!strcmp(data, "ledon"))
led2_on();
else if (!strcmp(data, "ledoff"))
led2_off();
else
ret = -EINVAL; // EINVAL “-” 返回负,返回非正常值
printk("led4 write ...\n");
return ret;
}
static int close(struct inode *node, struct file *file) {
led2_deinit();
printk("led4 close ...\n");
return 0;
}
static dev_t dev_num; //创建设备号
//操作方法
static struct file_operations fops = {
//结构体都是函数指针,上面函数指针都已经初始化了,
//所以赋给对应的结构体中函数指针
// gnu中,不像数据结构(windos)一个一个赋值,可以部分初始化
.owner = THIS_MODULE, //指向自己模块,默认
.open = open, //
.read = read,
.write = write,
.release = close};
static struct cdev cdev; //设备的结构体,
//(要把设备号和操作方法放进去),然后在给到内核
static struct class *led_class;
static struct device *led_device;
static int __init led_init(void) {
int ret = 0;
ret = alloc_chrdev_region(&dev_num, 0, 1, DEV_NAME);
if (ret < 0) {
printk("alloc_chrdev_region failed\n");
return ret;
}
cdev_init(&cdev,&fops); // cdev_init 把操作方法放进cdev结构体
ret = cdev_add(&cdev, dev_num,1); //添加几个设备?--和对应设备号放进cdev结构体
if (ret < 0) ////判断int类型
goto err_cdev;
led_class = class_create(THIS_MODULE, "led_class");
if (IS_ERR(led_class)) ////判断int类型
goto err_class;
led_device = device_create(led_class,NULL,dev_num,NULL,DEV_NAME);
if (IS_ERR(led_device))
goto err_device;
printk("led4_init ....\n");
return ret;
err_class:
unregister_chrdev_region(dev_num, 1); //取消注册
class_destroy(led_class);
printk("register_chrdev_region failed\n");
err_device:
unregister_chrdev_region(dev_num, 1);
device_destroy(led_class, dev_num);
printk("register_chrdev_region failed\n");
err_cdev:
cdev_del(&cdev); //销毁初始化的cdev结构体
printk("cdev_add failed\n");
return ret;
}
static void __exit led_exit(void) {
unregister_chrdev_region(dev_num, 1);
device_destroy(led_class, dev_num);
class_destroy(led_class);
cdev_del(&cdev);
printk("led4_exit ....\n");
}
module_init(led_init); //模块初始化 修饰对应的函数,然后执行对应函数
module_exit(led_exit); //在操作系统注销时和卸载 -- 启动和注销
MMODULE_LICENSE("GPL");
六:misc
杂项设备驱动
#include <linux/major.h>
#include <linux/miscdevice.h>
//不需要#define MAJOR/MINOP NUM
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static int __init led_init(void)
{
int ret = misc_register(&misc); //自动分配主设备号10,自动分配次设备号,//其中misc会把之前字符驱动的动作自动完成
if(ret < 0)
goto err_misc_register;
printk("led4_init ....\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("cmisc_deregister failed\n");
return ret;
}
static void __exit led_exit(void)
{
misc_deregister(&misc);
printk("led4_exit ....\n");
}
好处就是自动分配主设备号10,和随机分配次设备号
之后进入内核就不需要在mknod
节点,只需要insmod
就可以
七:字符和杂项驱动总结
1.字符设备驱动模板
#define DEV_NAME “led”
dev_t dev_num;
struct cdev cdev;
struct file_operations fops;
led_init()
{
MKDEV();
register_chrdev_region();
alloc_chrdev_register(); // /proc/devices DEV_NAME ,二选一
--------------------------------------------------------
cdev_init();
cdev_add();
class_create();
device_create(); // /dev/led DEV_NAME
}
led_exit()
{
//对应按相反顺序注销
}
module_init(led_init);
module_exit(led_exit);
2.杂项设备驱动(字符设备)
#define DEV_NAME “led”
struct miscdevice misc; // DEV_NAME
led_init()
{
misc_register(); // -- /dev/led
}
led_exit()
{
}
module_init(led_init);
module_exit(led_exit);
八:ioctl
ioctl
: 实现设置和属性获取方面的功能
dev | NUM | DIR | DATA |
---|---|---|---|
led1 | 0,1,2 | ||
led2 | 0,1,2,3 | ||
led3 | 0,1 |
1.代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#include <asm/ioctl.h>
#define DEV_NAME "led4_ioctl"
#define GPIO_LED1 S3C2410_GPB(5)
#define GPIO_LED2 S3C2410_GPB(6)
#define GPIO_LED3 S3C2410_GPB(7)
#define GPIO_LED4 S3C2410_GPB(8)
#define LED_ON 0
#define LED_OFF 1
#define LED_LED 2
#define MAGIC_NUM 'x'
#define CMD_LED_ON _IO(MAGIC_NUM, LED_ON)
#define CMD_LED_OFF _IO(MAGIC_NUM, LED_OFF)
#define CMD_LED_LED _IO(MAGIC_NUM, LED_LED)
static unsigned long led[4] = {GPIO_LED1, GPIO_LED2, GPIO_LED3, GPIO_LED4};
static void led2_init(void) {
int i = 0;
gpio_request(GPIO_LED1, "led1"); //申请
gpio_request(GPIO_LED2, "led2"); //申请
gpio_request(GPIO_LED3, "led3"); //申请
gpio_request(GPIO_LED4, "led4"); //申请
for (i = 0; i < 4; i++) {
gpio_direction_output(led[i], LED_OFF);//操作对应引脚
}
}
static void led2_on(unsigned char num)
{ gpio_set_value(led[num - 1], LED_ON); }
static void led2_off(unsigned char num)
{ gpio_set_value(led[num - 1], LED_OFF); }
static void led2_deinit(void) {
int i = 0;
for (i = 0; i < 4; i++) {
gpio_set_value(led[i], LED_OFF);
gpio_free(led[i]);
}
}
//以下都是函数指针
static int open(struct inode *node, struct file *file) {
led2_init();
printk("led4_ioctl open ...\n"); //内核打印
return 0;
}
static ssize_t read(struct file *file, char __user *buf, size_t len,
loff_t *offset) {
// copy_to_user();
printk("led4 read ...\n");
return 0;
}
static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
printk("led4 write ...\n");
return 0;
}
typedef struct __tag {
unsigned char l1;
unsigned char l2;
unsigned char l3;
unsigned char l4;
}led_t;
static led_t leda;
static long ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
int ret = 0;
printk("cmd = %d arg = %ld\n", cmd, arg);
switch (cmd) {
case CMD_LED_ON:
led2_on(arg);
break;
case CMD_LED_OFF:
led2_off(arg);
break;
case CMD_LED_LED:
copy_from_user(&leda, (led_t *)arg, sizeof(led_t));
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int close(struct inode *node, struct file *file) {
led2_deinit();
printk("led4 close ...\n");
return 0;
}
//操作方法
static struct file_operations fops = {
//结构体都是函数指针,上面函数指针都已经初始化了,
//所以赋给对应的结构体中函数指针
// gnu中,不像数据结构(windos)一个一个赋值,可以部分初始化
.owner = THIS_MODULE, //指向自己模块,默认
.open = open, //
.read = read,
.write = write,.unlocked_ioctl = ioctl, //操作方法链接ioctl
.release = close};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR, .name = DEV_NAME, .fops = &fops};
static int __init led_init(void) {
int ret = misc_register(&misc); //自动分配主设备号10,自动分配次设备号
//其中misc会把之前字符驱动的动作自动完成
if (ret < 0)
goto err_misc_register;
printk("led4_ioctl_init ....\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("cmisc_deregister failed\n");
return ret;
}
static void __exit led_exit(void) {
misc_deregister(&misc);
printk("led4_exit ....\n");
}
module_init(led_init); //模块初始化 修饰对应的函数,然后执行对应函数
module_exit(led_exit); //在操作系统注销时和卸载 -- 启动和注销
MODULE_LICENSE("GPL");
/
1.1 _IO宏
#define _IOC(dir,type,nr,size) \
((unsigned int) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT)))
//左移xx位,
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
//读
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
//写
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
//读写
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
2.配套配置文件
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#define LED_ON 0
#define LED_OFF 1
#define LED_LED 2
#define MAGIC_NUM 'x'
#define CMD_LED_ON _IO(MAGIC_NUM, LED_ON)
#define CMD_LED_OFF _IO(MAGIC_NUM, LED_OFF)
#define CMD_LED_LED _IO(MAGIC_NUM, LED_LED)
typedef struct __tag
{
unsigned char l1;
unsigned char l2;
unsigned char l3;
unsigned char l4;
}led_t;
static led_t led;
int main(int argc, const char *argv[])
{
int fd = open("/dev/led4_alloc",O_RDWR); ////打开对应内核文件
if(fd < 0)
{
perror("open demo failed");
}
unsigned char buf[20] = {0};
int ret = 0;
#if 1
while(1)
{
//ioctl(fd,1,1); //置电平1,led1
ioctl(fd,CMD_LED_ON,4);
sleep(1);
ioctl(fd,CMD_LED_OFF,4);
sleep(1);
led.l1 = 0;
led.l2 = 1;
ret = ioctl(fd,CMD_LED_LED,&led);//CMD_LED_LED 为2 系统忽律,
//要改在vim Documentation/ioctl/ioctl-number.txt
printf("ret = %d\n",ret);
sleep(1);
}
#endif
#if 0
while(1)
{
write(fd,"ledon",strlen("ledon"));
sleep(1);
write(fd,"ledoff",strlen("ledoff"));
sleep(1);
}
#endif
close(fd);
return 0;
}
3.文档
This table lists ioctls visible from user land for Linux/i386. It contains
most drivers up to 2.3.14, but I know I am missing some.
Code Seq# Include File Comments
========================================================
0x00 00-1F linux/fs.h conflict!
0x00 00-1F scsi/scsi_ioctl.h conflict!
0x00 00-1F linux/fb.h conflict!
0x00 00-1F linux/wavefront.h conflict!
0x02 all linux/fd.h
0x03 all linux/hdreg.h
0x04 D2-DC linux/umsdos_fs.h Dead since 2.6.11, but don't reuse these.
0x06 all linux/lp.h
0x09 all linux/md.h
0x12 all linux/fs.h
如果修改
eg: 'x' 00-1f 对应文件位置
//尽量不修改
九:irq_key
1 .inline
内联函数
函数体原地展开
2. waitevent
等待队列
static wait_queue_head_t wq;
static int condition;
condition = 1;
wake_up_interruptible(&wq);
wait_event_interruptible(wq,condition);
ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);
if(ret <0)
goto err_request_irq;
init_waitqueue_head(&wq);
printk("key4_init ....\n");
return ret;
err_request_irq:
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8,&arg);
printk("request_irq failed\n");
return ret;
2.1:代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#include <asm/ioctl.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#define DEV_NAME "key_irq"
static wait_queue_head_t wq;
static int condition;
irqreturn_t eint8_handler(int irq_num,void * dev)
{
unsigned int arg = *(unsigned int *)dev;
if(100 != arg)
{
return IRQ_NONE;
}
condition = 1;
wake_up_interruptible(&wq);
printk("irq_num = %d dev = %d\n",irq_num,arg);
return IRQ_HANDLED;
}
static void key2_init(void)
{
}
static void key2_deinit(void)
{
}
static int open(struct inode * node, struct file * file)
{
key2_init();
printk("key4 open ...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user();
printk("key4 read start\n");
condition = 0;
wait_event_interruptible(wq,condition);
printk("key4 read end...\n");
return 0;
}
static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
printk("key4 write ...\n");
return 0;
}
static int close(struct inode * node, struct file * file)
{
key2_deinit();
printk("key4 close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static unsigned int arg = 100;
static int __init key1_init(void)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc_register;
ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);
if(ret <0)
goto err_request_irq;
init_waitqueue_head(&wq);
printk("key4_init ....\n");
return ret;
err_request_irq:
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8,&arg);
printk("request_irq failed\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("misc_register faikey\n");
return ret;
}
static void __exit key1_exit(void)
{
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8,&arg);
misc_deregister(&misc);
printk("key4_exit ....\n");
}
module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");
2.2完整流程
1. 配套关系
在 Linux 内核里,等待/唤醒机制是典型的 进程同步机制,核心元素是:
等待队列(wait queue)
static wait_queue_head_t wq;
init_waitqueue_head(&wq);
它是一个链表,里面挂着所有等待某个条件的进程(task_struct)。
用于管理阻塞的进程。
条件变量(condition)
static int condition;
用来表示某个事件是否发生。
wait_event_interruptible() 会检查它,如果为假就让进程睡眠,否则直接返回。
阻塞等待函数
wait_event_interruptible(wq, condition);
宏会判断 condition 是否为真。
如果为假,就把调用进程放入 wq 队列,然后进入 可中断睡眠(TASK_INTERRUPTIBLE)。
当唤醒函数触发时,进程会被唤醒,再次检查 condition。
唤醒函数
wake_up_interruptible(&wq);
将 wq 队列里的所有可中断睡眠进程唤醒。
唤醒后的进程重新调度,继续执行 wait_event_interruptible() 后面的代码。
2. 结合你的代码流程
假设用户进程执行了 read():
condition = 0;
wait_event_interruptible(wq, condition);
流程拆解:
进程检查条件
condition == 0 → 条件不满足。
进程进入睡眠
进程状态变为 TASK_INTERRUPTIBLE。
内核把进程添加到 wq 队列里。
CPU 会调度其它可运行进程,当前进程阻塞。
中断触发
用户按键按下 → 外部中断触发 → 调用 eint8_handler()。
中断服务函数处理
condition = 1;
wake_up_interruptible(&wq);
设置条件为真(condition = 1)
唤醒等待队列中的所有进程
这些进程状态从 TASK_INTERRUPTIBLE → TASK_RUNNING
等待调度器分配 CPU 运行
进程被调度回 CPU
wait_event_interruptible() 重新检查条件
condition == 1 → 条件满足 → 返回
进程继续执行 read() 后面的代码
printk("key4 read end...\n");
3. wait_event / wake_up 配套总结
元素 作用
wait_queue_head_t wq 管理所有等待该事件的进程
condition 条件变量,表示事件是否发生
wait_event_interruptible(wq, condition) 阻塞调用进程直到条件成立
wake_up_interruptible(&wq) 唤醒等待队列里的所有进程,重新检查条件
关键点:
wait_event 宏里会不断检查条件,直到满足才返回 → 避免“虚假唤醒”问题。
wake_up_interruptible 只是将等待队列中的进程标记为可运行,实际调度由内核决定。
💡 总结一句话:
wait_event 是“睡觉等事件”,wake_up 是“事件来了叫醒睡着的进程”,二者配套实现了 中断到进程的异步通知机制。
3.中断底半部三种方式
上半部(Top Half)
→ 就是中断服务程序 (ISR)。
→ 要求执行尽量快,只做“紧急”的事,比如:
- 读取寄存器清中断
- 保存必要的数据到缓存
- 通知内核有事件发生
下半部(Bottom Half)
→ 处理耗时的部分,比如:
- 数据拷贝、复杂计算
- 和用户空间交互
//下半部3种实现方式
1.软中断
2.tasklet //基于软中断实现一种软中断
3.workqueue
4.异步调用
4.1 tasklet
Tasklet = 一种基于 Softirq 的轻量级下半部机制,不能睡眠
在同一个 CPU 上,同一个 tasklet 永远不会并行执行(保证了串行化)
串行化保证
- 内核保证 同一个 tasklet 在同一个 CPU 上不会并行执行。
- 如果某个 tasklet 正在跑,调度器不会在同一时刻再次运行它。
- 所以你可以放心在 tasklet 函数里操作它自己的数据,不用加锁。
- 这一点确实有点像“隐式的互斥”。
避免数据竞争
- 因为 tasklet 是单线程化的,使用它处理某个设备的中断数据,可以避免并发访问导致数据出错。
4.2 代码
static wait_queue_head_t wq;
static int condition;
static struct tasklet_struct task;
static void task_func(unsigned long arg)
{
ssleep(1);
condition = 1;
wake_up_interruptible(&wq);
printk("task_fun arg = %ld\n",arg);
}
static irqreturn_t eint8_handler(int irq_num,void * dev)
{
unsigned int arg = *(unsigned int *)dev;
if(100 != arg)
{
return IRQ_NONE;
}
tasklet_schedule(&task);
printk("irq_num = %d dev = %d\n",irq_num,arg);
return IRQ_HANDLED;
}
static int __init key1_init(void)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc_register;
ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);
if(ret <0)
goto err_request_irq;
init_waitqueue_head(&wq);
tasklet_init(&task,task_func,200);
printk("key4_init ....\n");
return ret;
err_request_irq:
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8,&arg);
printk("request_irq failed\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("misc_register faikey\n");
return ret;
}
//完整代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#include <asm/ioctl.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#define DEV_NAME "key_irq"
static wait_queue_head_t wq;
static int condition;
static struct tasklet_struct task;
static void task_func(unsigned long arg)
{
ssleep(1);
condition = 1;
wake_up_interruptible(&wq);
printk("task_fun arg = %ld\n",arg);
}
static irqreturn_t eint8_handler(int irq_num,void * dev)
{
unsigned int arg = *(unsigned int *)dev;
if(100 != arg)
{
return IRQ_NONE;
}
tasklet_schedule(&task);
printk("irq_num = %d dev = %d\n",irq_num,arg);
return IRQ_HANDLED;
}
static void key2_init(void)
{
}
static void key2_deinit(void)
{
}
static int open(struct inode * node, struct file * file)
{
key2_init();
printk("key4 open ...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user();
printk("key4 read start\n");
condition = 0;
wait_event_interruptible(wq,condition);
printk("key4 read end...\n");
return 0;
}
static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
printk("key4 write ...\n");
return 0;
}
static int close(struct inode * node, struct file * file)
{
key2_deinit();
printk("key4 close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static unsigned int arg = 100;
static int __init key1_init(void)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc_register;
ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);
if(ret <0)
goto err_request_irq;
init_waitqueue_head(&wq);
tasklet_init(&task,task_func,200);
printk("key4_init ....\n");
return ret;
err_request_irq:
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8,&arg);
printk("request_irq failed\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("misc_register faikey\n");
return ret;
}
static void __exit key1_exit(void)
{
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8,&arg);
misc_deregister(&misc);
printk("key4_exit ....\n");
}
module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");
4.3 workqueue
Tasklet 虽然能把工作延后,但它仍然运行在 中断上下文:
- 不能睡眠
- 不能调度别的进程
- 不能调用可能阻塞的函数(例如
kmalloc(GFP_KERNEL)
,copy_to_user
,mutex_lock
) - 如果我们在下半部需要做一些 耗时或可能阻塞的工作(比如磁盘 IO,文件操作,访问用户空间),就必须要有一个能在 进程上下文 运行的机制(Workqueue)。
Workqueue 的特点
- 运行在内核线程(kworker)里,属于 进程上下文。
- 可以睡眠(这是和 tasklet 的最大区别)。
- 内核会维护一组 kworker 线程,专门执行 work。
4.4 代码
static wait_queue_head_t wq;
static int condition = 0;
static struct work_struct work;
static void work_func(unsigned long arg)
{
ssleep(1);
condition = 1;
wake_up_interruptible(&wq);
printk("task_fun arg = %ld\n",arg);
}
static irqreturn_t eint8_handler(int irq_num,void * dev)
{
unsigned int arg = *(unsigned int *)dev;
if(100 != arg)
{
return IRQ_NONE;
}
schedule_work(&work);
printk("irq_num = %d dev = %d\n",irq_num,arg);
return IRQ_HANDLED;
}
init_waitqueue_head(&wq);
INIT_WORK(&work,work_func);
完整代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#include <asm/ioctl.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#define DEV_NAME "key"
static wait_queue_head_t wq;
static int condition = 0;
static struct work_struct work;
static void work_func(unsigned long arg)
{
ssleep(1);
condition = 1;
wake_up_interruptible(&wq);
printk("task_fun arg = %ld\n",arg);
}
static irqreturn_t eint8_handler(int irq_num,void * dev)
{
unsigned int arg = *(unsigned int *)dev;
if(100 != arg)
{
return IRQ_NONE;
}
schedule_work(&work);
printk("irq_num = %d dev = %d\n",irq_num,arg);
return IRQ_HANDLED;
}
static void key2_init(void)
{
}
static void key2_deinit(void)
{
}
static int open(struct inode * node, struct file * file)
{
key2_init();
printk("key4_work open ...\n");
return 0;
}
static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
//copy_to_user();
printk("key4 read start\n");
condition = 0;
wait_event_interruptible(wq,condition);
printk("key4 read end...\n");
return 0;
}
static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
printk("key4 write ...\n");
return 0;
}
static int close(struct inode * node, struct file * file)
{
key2_deinit();
printk("key4 close ...\n");
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = open,
.read = read,
.write = write,
.release = close
};
static struct miscdevice misc =
{
.minor = MISC_DYNAMIC_MINOR,
.name = DEV_NAME,
.fops = &fops
};
static unsigned int arg = 100;
static int __init key1_init(void)
{
int ret = misc_register(&misc);
if(ret < 0)
goto err_misc_register;
ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);
if(ret <0)
goto err_request_irq;
init_waitqueue_head(&wq);
INIT_WORK(&work,work_func);
printk("key4_init ....\n");
return ret;
err_request_irq:
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8,&arg);
printk("request_irq failed\n");
return ret;
err_misc_register:
misc_deregister(&misc);
printk("misc_register faikey\n");
return ret;
}
static void __exit key1_exit(void)
{
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8,&arg);
misc_deregister(&misc);
printk("key4_exit ....\n");
}
module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");
4.5两者区别
特性 | Tasklet | Workqueue | |
---|---|---|---|
执行上下文 | 软中断(不可睡眠) | 内核线程(可睡眠) | |
互斥性 | 同一 tasklet 永远不会并行 | 同一个 work 不会并行,不同 work 可并行 | |
并发性 | 多个 tasklet 可在不同 CPU 并发 | 多个 work 可在多个 worker 线程并发 | |
调度粒度 | 基于 softirq,立即调度 | 基于调度器,可延迟 | |
灵活性 | 简单、轻量 | 丰富(延迟、优先级、CPU 绑定) | |
典型用途 | 中断下半部、快速轻量任务 | 复杂/耗时任务,需要睡眠的场景 | |
上下文 | 软中断(softirq)上下文,属于 中断上下文 | 运行在内核线程(kworker)上下文,属于 进程上下文 |