03-中斷的下半部 workqueue

本系列文章主要講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
發佈了57 篇原創文章 · 獲贊 65 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章