本系列文章主要讲Linux中的中断和时间管理,文章机构如下:
01 - 驱动中的中断处理
02 - 中断下半部 tasklet
03 - 中断的下半部 workqueue
04 - Linux中的延时操作函数
05 - Linux硬件定时 jiffies
06 - Linux 低分辨率定时器
07 - Linux高分辨率定时器
文章目录
上个示例讲的软中断不管是软中断还是 tasklet 都有一个限制,就是在中断的上下文不能直接或者间接的调用调度器,为了解决这个问题,内核中又提供了一种下半部机制——工作队列。
1. 内核中的工作队列
工作队列就是内核在启动的时候创建一个或多个内核工作线程,工作线程取出内核中的每一个工作然后执行,当队列中没有工作时线程休眠,当线程想要执行某一个工作时,创建一个工作队列节点对象,然后加入到相应的工作队列,并唤醒工作线程,然后工作线程取出工作队列上的节点来完成工作,所有工作完成后又休眠。因为工作队列是运行在进程上下文中因此可以调用调度器,工作队列提供了一种延时执行的机制,这种机制也适用于中断的下半部。
工作队列节点的定义如下(include\linux\workqueue.h)
struct work_struct {
atomic_long_t data; // 传递给工作函数的参数,常用指针与 ioctl 的最后一个参数类似
struct list_head entry; // 构成工作队列的链表结点对象
work_func_t func; // 工作函数,工作线程取出工作队列节点后执行,data会作为该函数的参数
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
常用的API接口如下:
1.1 静态定义一个工作队列节点
DECLARE_WORK(n, f) // 静态定义一个工作队列节点,n是工作队列的名字,f是工作函数
宏的原型如下:
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
#define __WORK_INITIALIZER(n, f) { \
.data = WORK_DATA_STATIC_INIT(), \
.entry = { &(n).entry, &(n).entry }, \
.func = (f), \
__WORK_INIT_LOCKDEP_MAP(#n, &(n)) \
}
1.2 静态定义一个延时的工作队列节点
DECLARE_DELAYED_WORK(n, f) // 静态定义一个延时的工作队列节点
#define DECLARE_DELAYED_WORK(n, f) struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
struct delayed_work {
struct work_struct work;
struct timer_list timer;
// target workqueue and CPU ->timer uses to queue ->work
struct workqueue_struct *wq;
int cpu;
};
#define __DELAYED_WORK_INITIALIZER(n, f, tflags) { \
.work = __WORK_INITIALIZER((n).work, (f)), \
.timer = __TIMER_INITIALIZER(delayed_work_timer_fn, 0, (unsigned long)&(n), (tflags) | TIMER_IRQSAFE), \
}
1.3 动态分配工作队列节点
INIT_WORK(_work, _func) // 动态分配工作队列节点的初始化
1.4 将工作队列加入到内核定义的全局工作队列中
bool schedule_work(struct work_struct * work) // 将工作队列加入到内核定义的全局工作队列中
bool schedule_delayed_work(struct delayed_work * dwork, unsigned long delay) // 在delay指定的时间后讲一个延时工作队列节点插入到全局工作队列中
2 示例代码
2.1 demo.c
在代码22行静态定义一个工作队列,名字是test_workqueue;在代码56行中断处理函数中将工作队列加入到内核定义的全局工作队列中,然后将原本在中断中处理的事情交给工作队列函数中处理(代码61行)
#include <linux/module.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/atomic.h>
#include <linux/slab.h> // kzalloc和kfree的头文件
#include <linux/interrupt.h> // 中断相关的头文件
typedef struct
{
dev_t dev_no;
char devname[20];
char classname[20];
struct cdev demo_cdev;
struct class *cls;
struct device *dev;
struct mutex my_mutex; // 定义一个互斥体,在init函数中进行初始化
}demo_struct;
demo_struct my_demo_dev; // 定义一个设备结构体指针,指向NULL
static void func_wrokqueue(struct work_struct *); // 工作队列函数的声明
DECLARE_WORK(test_workqueue, func_wrokqueue); // 静态定义一个工作队列,名字是test_workqueue
static int demo_open(struct inode *inode, struct file *filp)
{
/* 首先判断设备是否可用 */
if( mutex_lock_interruptible(&my_demo_dev.my_mutex) ) // 访问共享资源之前获取互斥体,成功获取返回0
{
return -ERESTARTSYS; // 不能获取返回错误码
}
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
static int demo_release(struct inode *inode, struct file *filp)
{
/* 释放互斥量 */
mutex_unlock(&my_demo_dev.my_mutex);
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
return 0;
}
struct file_operations demo_ops = {
.open = demo_open,
.release= demo_release,
};
irqreturn_t my_irq_handler(int irq, void *dev_id)
{
schedule_work(&test_workqueue); // 将工作队列加入到内核定义的全局工作队列中
return IRQ_RETVAL(IRQ_HANDLED); // 中断处理函数常用的返回值
}
static void func_wrokqueue(struct work_struct *work)
{
/*
将上半部的事情放到下半部执行
*/
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
}
static int __init demo_init(void)
{
int ret;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
strcpy(my_demo_dev.devname, "demo_chrdev"); // 给设备名字赋值
strcpy(my_demo_dev.classname, "demo_class"); // 给设备类的名字赋值
mutex_init(&my_demo_dev.my_mutex); // 初始化互斥体
ret = alloc_chrdev_region(&my_demo_dev.dev_no, 0, 0, my_demo_dev.devname);
if (ret)
{
printk("alloc_chrdev_region failed.\n");
goto region_err;
}
cdev_init(&my_demo_dev.demo_cdev, &demo_ops);
ret = cdev_add(&my_demo_dev.demo_cdev, my_demo_dev.dev_no, 1);
if (ret < 0)
{
printk("cdev_add failed.\n");
goto add_err;
}
my_demo_dev.cls = class_create(THIS_MODULE, my_demo_dev.classname); /* 在目录/sys/class/.. */
if ( IS_ERR(my_demo_dev.cls) )
{
ret = PTR_ERR(my_demo_dev.cls);
printk("class_create failed.\n");
goto cls_err;
}
my_demo_dev.dev = device_create(my_demo_dev.cls, NULL, my_demo_dev.dev_no, NULL, "chrdev%d", 0); /* 在目录/dev/.. */
if ( IS_ERR(my_demo_dev.dev) )
{
ret = PTR_ERR(my_demo_dev.dev);
printk("device_create failed.\n");
goto dev_err;
}
// 注册中断,因为是共享中断所以必须传递dev_id
ret = request_irq(123, my_irq_handler, IRQF_TRIGGER_RISING | IRQF_SHARED, "irq_test", &my_demo_dev);
if ( ret ) // 成功返回0,失败返回错误码
{
printk("request_irq failed.\n");
goto irq_err;
}
return 0;
irq_err:
device_destroy(my_demo_dev.cls, my_demo_dev.dev_no);
dev_err:
class_destroy(my_demo_dev.cls);
cls_err:
cdev_del(&my_demo_dev.demo_cdev);
add_err:
unregister_chrdev_region(my_demo_dev.dev_no, 1);
region_err:
return ret;
}
static void __exit demo_exit(void)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
free_irq(123, &my_demo_dev); // 注销中断
device_destroy(my_demo_dev.cls, my_demo_dev.dev_no);
class_destroy(my_demo_dev.cls);
cdev_del(&my_demo_dev.demo_cdev);
unregister_chrdev_region(my_demo_dev.dev_no, 1);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
2.2 test.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd;
fd = open("/dev/chrdev0", O_RDWR, 0666);
if (fd < 0)
{
perror("open");
return -1;
}
sleep(10);
close(fd);
return 0;
}
2.3 Makefile
KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)
all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
arm-linux-gnueabihf-gcc test.c -o app
install:
sudo cp *.ko app /tftpboot
clean:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
obj-m += demo.o