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

五:动态加载驱动

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: 实现设置和属性获取方面的功能

devNUMDIRDATA
led10,1,2
led20,1,2,3
led30,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两者区别
特性TaskletWorkqueue
执行上下文软中断(不可睡眠)内核线程(可睡眠)
互斥性同一 tasklet 永远不会并行同一个 work 不会并行,不同 work 可并行
并发性多个 tasklet 可在不同 CPU 并发多个 work 可在多个 worker 线程并发
调度粒度基于 softirq,立即调度基于调度器,可延迟
灵活性简单、轻量丰富(延迟、优先级、CPU 绑定)
典型用途中断下半部、快速轻量任务复杂/耗时任务,需要睡眠的场景
上下文软中断(softirq)上下文,属于 中断上下文运行在内核线程(kworker)上下文,属于 进程上下文
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值