02-中斷下半部 tasklet

本系列文章主要講Linux中的中斷和時間管理,文章機構如下:
01 - 驅動中的中斷處理
02 - 中斷下半部 tasklet
03 - 中斷的下半部 workqueue
04 - Linux中的延時操作函數
05 - Linux硬件定時 jiffies
06 - Linux 低分辨率定時器
07 - Linux高分辨率定時器

1. 中斷的上下部

 在上個例程提到,中斷處理函數應該儘快完成,否則會影響對其他中斷的及時響應,影響系統的性能。但有時候這些耗時的操作又避免不了,比如電容觸摸屏通過中斷通知 SOC 有觸摸事件發生,SOC 響應中斷,然後通過 IIC 接口讀取觸摸座標值並將其上報給系統。但是我們都知道 IIC 的速度最高也只有400Kbit/S,所以在中斷中通過 IIC 讀取數據就會浪費時間。爲了解決這個問題,Linux將中斷分爲了兩部分,上半部和下半部,上半部能完成緊急但很快能完成的事情,下半部完成不緊急但比較耗時的事情。對於電容觸摸屏來講可以將通過 IIC 讀取觸摸數據的操作暫後執行,中斷處理函數僅僅相應中斷,然後清除中斷標誌位即可。

中斷上半部和下半部的區分可參考如下:
 ①、如果要處理的內容不希望被其他中斷打斷,那麼可以放到上半部。
 ②、如果要處理的任務對時間敏感,可以放到上半部。
 ③、如果要處理的任務與硬件有關,可以放到上半部
 ④、除了上述三點以外的其他任務,優先考慮放到下半部。

2. 軟中斷

 在學習 tasklet 之前,首先了解一下軟中斷,軟中斷是中斷下半部機制的一種,目前內核中定義軟中斷的編號如下:

enum
{
	HI_SOFTIRQ=0, 		// 高優先級軟中斷 
	TIMER_SOFTIRQ, 		// 定時器軟中斷 
	NET_TX_SOFTIRQ, 	// 網絡數據發送軟中斷 
	NET_RX_SOFTIRQ, 	// 網絡數據接收軟中斷 
	BLOCK_SOFTIRQ,
	BLOCK_IOPOLL_SOFTIRQ,
	TASKLET_SOFTIRQ, 	// tasklet 軟中斷 
	SCHED_SOFTIRQ, 		// 調度軟中斷 
	HRTIMER_SOFTIRQ, 	// 高精度定時器軟中斷  
	RCU_SOFTIRQ, 		// RCU 軟中斷  

	NR_SOFTIRQS
};

3. tasklet

 軟中斷通常由內核的開發者設計,內核開發者保留了一個軟中斷給驅動的開發者,他就是 TASKLET_SOFTIRQ(tasklet 軟中斷)內核中對 tasklet 的處理函數和宏定義如下:

struct tasklet_struct
{
	struct tasklet_struct *next;	// 構成鏈表的指針
	unsigned long state;			// tasklet被調度的狀態,已經被調度還是已經在執行
	atomic_t count;					// 用於禁止tasklet執行(非0時禁止執行)
	void (*func)(unsigned long);	// tasklet下半部的函數
	unsigned long data;				// 傳遞給下半部的參數
};

內核中定義的tasklet相關函數如下:

DECLARE_TASKLET(name, func, data)	// 靜態定義一個 tasklet_struct 結構體,名字爲name,下半部函數爲func,傳遞的數據爲data
DECLARE_TASKLET_DISABLED(name, func, data)	// 定義 tasklet_struct 結構體,count被賦值爲1,不能被執行,需要調用 tasklet_enable 使能	
// 宏定義的原型如下
#define DECLARE_TASKLET(name, func, data) \
	struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \
	struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

void tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)	// 初始化動態分配 tasklet_struct 結構體
inline void tasklet_schedule(struct tasklet_struct * t)	// 將指定的 tasklet_struct 結構體對象加入到tasklet的鏈表中,下半部函數將會在未來的某個時間被調度

4. 示例代碼

4.1 demo.c

 在代碼的22行靜態定義一個tasklet,在代碼的55行將指定的tasklet_struct 結構體對象加入到tasklet的鏈表中,下半部函數將會在未來的某個時間被調度,在函數 func_tasklet 中將原本屬於中斷函數處理的內容交給中斷的下半部函數處理

#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_tasklet(unsigned long args);									 // 中斷下半部函數的聲明 
DECLARE_TASKLET(tasklet_test, func_tasklet, (unsigned long)&my_demo_dev);	// 靜態定義一個tasklet

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)
{
	tasklet_schedule(&tasklet_test);	// 將指定的 tasklet_struct 結構體對象加入到tasklet的鏈表中,下半部函數將會在未來的某個時間被調度

	return IRQ_RETVAL(IRQ_HANDLED);		// 中斷處理函數常用的返回值
}

static void func_tasklet(unsigned long args)
{
	/*
		將上半部的事情放到下半部執行
	*/
	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");

4.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(60);

	close(fd);

	return 0;
}

4.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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章