动态加载和异步调用tasklet/workqueue day63 ay64

发布于:2025-08-29 ⋅ 阅读:(15) ⋅ 点赞:(0)

五:动态加载驱动

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_usermutex_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)上下文,属于 进程上下文

网站公告

今日签到

点亮在社区的每一天
去签到