Linux_kernel中断系统13

一、温故知新

1、字符设备编程框架

什么是字符设备?

在Linux操作系统中文件类型为**.c**文件,称为字符设备文件

按字节访问 访问的顺序是固定的

1)struct cdev结构

实现一个硬件字符设备的驱动程序时,实际上就是实例化一个struct cdev类型的对象

struct cdev {

const struct file_operations *ops; // 操作函数集合

dev_t dev; // 设备号

}

在实例化时,我们只需关注这两个成员即可

2)dev(设备号)

dev_t dev;

dev_t类型是无符号整型,是32bit的

设备号分为主设备号和次设备号

dev(设备号) = 主设备号(12bit [msb]) + 次设备号(20bit [lsb])

【1】静态注册

静态注册:程序员自己选择一个没有被内核占用且符合规定的设备号去注册

register_chrdev_region()

unregister_chrdev_region()

【2】动态注册

动态注册:内核自动分配一个设备号给我们使用

alloc_chrdev_region()

unregister_chrdev_region()

【3】内核中封装的宏

MKDEV宏:可以把主设备号和次设备号组合到一起

MAJOR宏:从设备号中提取主设备号

MINOR宏:从设备号中提取次设备号

3)ops(操作函数集合)

在struct file_operations结构中,几乎都是函数指针

当我们需要实现一个字符设备驱动的时候,

我们主要的任务就是实现操作函数集合中的函数

4)操作cdev对象的函数

cdev_init(); // 初始化

cdev_add(); // 将cdev对象添加到内核

cdev_del(); // 注销cdev对象

2、GPIO库的使用

寄存器在内核态开发时,无需再关注

因为Linux内核为程序员封装了GPIO相关的库可以使用

GPIO库的函数

gpio_request() // 申请gpio管脚

gpio_direction_input() // 设置gpio为输入模式

gpio_direction_output() // 设置gpio为输出模式

gpio_set_value() // 设置gpio的值

gpio_get_value() // 读取gpio的值

gpio_free() // 注销gpio管脚

3、用户态与内核态的数据交互

用户态与内核态交互的媒介:/dev/myleds(需要手动创建)

mknod /dev/myleds c major minor

major:主设备号

minor:次设备号

需要与cdev注册的设备号相同(不同的话,会报找不到设备文件)

1)数据交互

用户空间不能直接访问内核空间

内核空间不能直接访问用户空间

2)内核提供数据交互的接口

copy_to_user() // 从内核空间到用户空间

copy_from_user() // 从用户空间到内核空间

二、设备文件的自动创建

1、必备条件

1)根文件系统

【1】支持mdev命令

ls -l /sbin/mdev

【2】挂载proc以及sysfs

cat /etc/fstab

proc:是基于内存的文件系统(动态)

可以向用户态导出内核的执行状态

sysfs:是基于内存的文件系统(动态)

描述硬件的驱动模型,可以反映出硬件的层次关系

【3】支持热插拔事件

cat /etc/init.d/rcS

2)驱动程序

产生热插拔事件

注释:热插拔事件

狭义:USB设备的插拔

广义:/sys 目录下的文件变化
我们将设备看成是一棵树

【1】class_create()(创建树枝)

用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进Linux内核系统中。此函数的执行效果就是在目录**/sys/class**下创建一个新的文件夹,此文件夹的名字为此函数的第二个输入参数,但此文件夹是空的。

class_create(owner, name)

【2】device_create()(创建果实)

用于动态地创建逻辑设备,并对新的逻辑设备类进行相应的初始化,将其与此函数的第一个参数所代表的逻辑类关联起来,然后将此逻辑设备加到Linux内核系统的设备驱动程序模型中。函数能够自动地在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建与逻辑类对应的设备文件。

cpp 复制代码
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
【3】device_destroy()(销毁果实)

用于从Linux内核系统设备驱动程序模型中移除一个设备,并删除/sys/devices/virtual目录下对应的设备目录及/dev目录下对应的设备文件。

void device_destroy(struct class *class, dev_t devt)

【4】class_destroy()(销毁树枝)

用于删除设备的逻辑类,即从Linux内核系统中删除设备的逻辑类。此函数执行的效果是删除函数__class_create()或宏class_create()在目录/sys/class下创建的逻辑类对应的文件夹。

void class_destroy(struct class *cls)

3)总结
【1】mdev

在系统启动 \ 热插拔 和动态加载模块时,自动创建设备节点

文件系统中的/dev目录下的设备节点都是由mdev创建的

在加载模块时根据驱动程序,可以在/dev/目录下自动创建设备文件

【2】class(树枝)

内核中定义了一个struct class结构体,class_create()函数可以实例化这个结构体,并将这个类存放在sysfs虚拟系统中。

通过class_create()注册/sys/class/<name>

通过class_destory()注销/sys/class/<name>

【3】device(果实)

在class_create()创建好类之后,再调用device_create()函数

系统会自动在/dev目录下创建相应的设备节点

根目录在加载模块时,用户空间中的mdev(设备管理器)会自动响应device_create()函数

去/sys目录下找对应的类从而创建设备文件

2、实验

【1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

【2】创建新的工程

mkdir auto_drv

【3】编写程序

vim auto_drv.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/uaccess.h>
#include <linux/device.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

#define CHRDEV_MAGOR	200
#define CHRDEV_MINOR	26
#define CHRDEV_NUM		1
#define CHRDEV_NAME		"myleds"
#define HIGH			1
#define LOW				0
#define LED0	(PAD_GPIO_B + 26)
#define LED1	(PAD_GPIO_C + 12)
#define LED2	(PAD_GPIO_C + 7)
#define LED3	(PAD_GPIO_C + 11)
#define TURN_ON			LOW
#define TURN_OFF		HIGH



dev_t dev = 0;

struct cdev led_cdev;

// define the global var of struct class
struct class *cls = NULL;

typedef struct led_desc{
	unsigned int gpio;
	char *name;
}led_desc_t;

led_desc_t leds[] = {
	{LED0, "LED0"},
	{LED1, "LED1"},
	{LED2, "LED2"},
	{LED3, "LED3"}
};

long led_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
	int k_index = 0;
	int ret = 0;

	ret = copy_from_user(&k_index, (const void *)arg, sizeof(int));
	if (k_index > 4 || k_index < 1)
		return -EINVAL;
	
	switch (cmd) {
		case TURN_ON:
			gpio_set_value(leds[k_index - 1].gpio, LOW);
			break;
		case TURN_OFF:
			gpio_set_value(leds[k_index - 1].gpio, HIGH);
			break;
		default:
			return -EINVAL;
	}

	return arg;
}

struct file_operations led_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = led_ioctl
};

int __init chrdev_init(void)
{
	int major = CHRDEV_MAGOR;
	int minor = CHRDEV_MINOR;
	int i = 0;

	alloc_chrdev_region(&dev, CHRDEV_MINOR, CHRDEV_NUM, CHRDEV_NAME);
	major = MAJOR(dev);
	minor = MINOR(dev);

	printk(KERN_EMERG "major = %d\nminor = %d\n", major, minor);

	cdev_init(&led_cdev, &led_fops);

	cdev_add(&led_cdev, dev, CHRDEV_NUM);

	// auto mknod the device file
	// ctreat branch
	cls = class_create(THIS_MODULE, "easthome_leds");
	// create fruit
	device_create(cls, NULL, dev, NULL, "myleds");

	for (i = 0; i < ARRAY_SIZE(leds); i++) {
		gpio_request(leds[i].gpio, leds[i].name);
		gpio_direction_output(leds[i].gpio, HIGH);
	}

	return 0;
}

void __exit chrdev_exit(void)
{
	int i = 0;

	for (i = 0; i < ARRAY_SIZE(leds); i++) {
		gpio_free(leds[i].gpio);
	}
	// auto delete the device file
	// destory fruit
	device_destroy(cls, dev);
	// destory branch
	class_destroy(cls);

	cdev_del(&led_cdev);
	unregister_chrdev_region(dev, CHRDEV_NUM);

	return ;
}

module_init(chrdev_init);
module_exit(chrdev_exit);
【4】编写Makefile

vim Makefile

bash 复制代码
obj-m += auto_drv.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean
【5】编译工程

make

【6】编写应用层程序

mkdir test

cd test

vim led_test.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <stdlib.h>

#define ON		0
#define OFF		1
#define CDEV_PATH	"/dev/myleds"

int main(int argc, char *argv[])
{
	int fd = 0;
	int cmd = 0;
	int index = 0;

	if (argc < 3) {
		printf("Usage : %s <on/off> <1/2/3/4>\n", argv[0]);
		return -1;
	}

	if (!strcmp(argv[1], "on")) {
		cmd = ON;
	} else if (!strcmp(argv[1], "off")){
		cmd = OFF;
	} else {
		printf("illegal param\n");
		return -2;
	}
	
	index = atoi(argv[2]);
	if (index < 1 || index > 4) {
		printf("illegal param\n");
		return -2;
	}

	if((fd = open(CDEV_PATH, O_RDWR)) < 0) {
		perror("open()");
		return -3;
	}
	printf("open success!\n");

	ioctl(fd, cmd, &index);

	printf("closing...\n");

	close(fd);

	return 0;
}

vim Makefile

bash 复制代码
SRC=led_test.c
OBJ=led_test

ARM_COMPILE=arm-cortex_a9-linux-gnueabi-
GCC=gcc

ROOTFS_PATH=/nfs_share/_install

all:
	$(ARM_COMPILE)$(GCC) $(SRC) -o $(OBJ)
	cp $(OBJ) $(ROOTFS_PATH)

clean:
	rm -rf $(OBJ)
【7】编译工程

make

【8】下位机测试

insmod auto.drv.ko

./led_test

三、远程登录开发板

1、准备rootfs

cp /mnt/hgfs/music/easthome_porting/rootfs_1204.tar.gz ./

1)解压缩

tar -xvf rootfs_1204.tar.gz

2)nfs挂载

sudo vim /etc/exports

3)重启服务

sudo /etc/init.d/nfs-kernel-server restart

4)关闭防火墙

sudo /etc/init.d/ufw stop

5)修改下位机环境变量

bash 复制代码
setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/nfs_share/rootfs ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 init=linuxrc console=ttySAC0,115200 maxcpus=1 lcd=wy070ml tp=gslx680

saveenv

re

2、telnet

1)下位机开启server

vi etc/init.d/rcS

2)确保存在devpt目录

vi etc/init.d/rcS

注释:

pts是远程虚拟终端

devpts是远程终端的文件设备

通过挂载到/dev/pts可以了解到目前虚拟终端的基本情况

3)下位机的根文件系统有登录检验机制

vi etc/passwd

4)验证

【1】保证下位机可以ping通上位机

ping 192.168.1.8

【2】上位机执行

telent 192.168.1.6

3、ssh

SSH(Secure Shell)是一种网络协议,用于在网络上安全地进行远程登录和执行命令。它通过加密技术保护数据传输的安全性,使得用户可以在不安全的网络中安全地访问远程主机。SSH可以提供对远程主机的终端访问、文件传输和端口转发等功能,被广泛应用于服务器管理、系统维护和远程操作等场景。

1)安装ssh

sudo apt install openssh-client


如果需要被操纵,执行以下指令

sudo apt install openssh-server


2)验证

ssh

3)开启服务

service sshd start

或:

sudo /etc/init.d/ssh start

4)连接

ssh username@server-ip-address

如:ssh root@192.168.1.6

四、Linux内核中的中断子系统

裸板中的中断处理过程

1、中断的触发

1)中断源级

配置中断的触发方式:上升沿、下降沿、双边沿、高电平、低电平

中断触发(检测到中断信号之后,判断能不能报给CPU CORE)

2)中断控制器级

配置中断的优先级

中断使能

配置以IRQ FIQ的方式报给 CPU CORE

配置报给哪个CPU CORE

3)ARM CORE级

配置寄存器

cpsr.i = 0

中断的使能 I = 0

2、裸板中断处理过程

中断异常发生之后,硬件上自动做4件事儿

1)将cspr备份到spsr

2)修改cpsr的一些位

MODE模式

I 禁止IRQ

F 禁止FIQ

T 切换到ARM工作状态

3)保存返回的地址到 lr 寄存器中

4)跳转到异常向量表中执行

ldr pc, =irq_handler

irq_handler:

现场保护

bl c_irq_handler

恢复现场

c_irq_handler

{

区分哪个硬件出发的irq信号

调用该硬件的中断处理函数

清除pending位

}

3、Linux中断处理过程

Linux的中断处理过程与裸板的中断处理过程是相同的,linux kernel将能够帮程序员写的代码,都写好了,写好的这一部分,就叫做"Linux中断子系统"

注意:

特定硬件的中断的触发方式,需要自行配置(上升沿、下降沿、双边沿、高电平、低电平)

特定硬件的中断处理函数需要自己编写代码来实现

1)中断注册函数
cpp 复制代码
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

注释:

static:声明为静态函数

inline:C++关键字,将函数指定为内联函数


使用inline关键字修饰的函数,编译器在进行编译时,会直接将该函数放在代码段中,省去了函数调用时的开销,典型的空间换时间,比较适用于代码段较小,但调用次数较多且较为耗时的函数。

以下两种情景不适合做内联优化:

【1】代码段很大且不经常使用的函数

【2】递归函数


__must_check:用于表明,如果调用我修饰的函数,则调用者必须对返回值进行处理,否则就会给出警告

irq:中断号


1】将gpio转换为irq

gpio_to_irq(gpio)

2】将irq转换为gpio

irq_to_gpio(irq)

3】查看开发板定义的中断号

vim kernel/arch/arm/mach-s5p6818/include/mach/s5p6818_irq.h


handler:要注册的中断处理函数


返回值为 irqreturn_t类型


flsgs:自行配置的中断触发方式


vim kernel/include/linux/interrupt.h


name:要注册的中断的名称

dev:要传递给自己注册的中断处理函数的第二个参数

2)中断注销函数

void free_irq(unsigned int irq, void *dev_id)

注释:

irq:中断号

dev_id:要传递给自己注册的中断处理函数的第二个参数

3)实验
【1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

【2】创建新的工程

mkdir interrupt_btn

【3】编写程序

vim interrupt_btn.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <mach/platform.h>
#include <linux/interrupt.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

#define IRQ_GPIOA28		(IRQ_GPIO_A_START + 28)
#define IRQ_GPIOB30		(IRQ_GPIO_B_START + 30)
#define IRQ_GPIOB31		(IRQ_GPIO_B_START + 31)
#define IRQ_GPIOB9		(IRQ_GPIO_B_START + 9)

#define BTN_UP			IRQ_GPIOA28
#define BTN_DOWN		IRQ_GPIOB30
#define BTN_LEFT		IRQ_GPIOB31
#define BTN_RIGHT		IRQ_GPIOB9 


// define a struct btn_desc to save the info of irq and name
typedef struct btn_desc {
	int irq;
	char *name;
}btn_desc_t;

btn_desc_t btns[] = {
	{BTN_UP	 , "up"},
	{BTN_DOWN , "down"},
	{BTN_LEFT , "left"},
	{BTN_RIGHT, "right"}
};

// register the interrupt handler
irqreturn_t btn_irq(int irq, void *data)
{
	btn_desc_t *pdata = (btn_desc_t *)data;

	printk(KERN_EMERG "%s is pressed!\n", pdata->name);

	return IRQ_HANDLED;
}

int __init interrupt_btn_init(void)
{
	int i = 0;
	int ret = 0;	// recive the ret value

	for (i = 0; i < ARRAY_SIZE(btns); i++) {
		ret = request_irq(btns[i].irq, btn_irq, IRQF_TRIGGER_FALLING, btns[i].name, &(btns[i]));
		if (ret) {
			printk(KERN_EMERG "request_irq is failed!\n");
			i --;
			while (i >= 0) {
				// if register failed, we should destory the irq
				free_irq(btns[i].irq, &(btns[i]));
				i --;
			}

			return -EAGAIN;
		}
	}

	return 0;
}

void __exit interrupt_btn_exit(void)
{
	int i = 0;

	for (i = 0; i < ARRAY_SIZE(btns); i++) {
		free_irq(btns[i].irq, &(btns[i]));
	}

	return ;
}

module_init(interrupt_btn_init);
module_exit(interrupt_btn_exit);
【4】编写Makefile

vim Makefile

bash 复制代码
obj-m += interrupt_btn.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/rootfs

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean
【5】编译工程

make

【6】下位机测试

telnet 192.168.1.6

insmod interrupt_btn.ko

【7】调试

原因:模块冲突,内核中自带了按键驱动程序

解决方案:裁剪内核

1】cd kernel

2】make menuconfig

make menuconfig

Device Drivers --->

Input device support --->

[*] Keyboards --->

< > SLsiAP push Keypad support

3】上位机拷贝内核至下位机

make uImage

cp arch/arm/boot/uImage /tftpboot/

4】下位机更新内核

tftp 48000000 uImage

mmc write 48000000 2000 3000

re

5】再次验证

4、总结

中断处理函数存在的疑虑

【1】Linux系统希望中断上下文处理的越快越好(不应该有printk函数,其不具可重入性)

【2】中断处理函数中不应该有返回值

【3】中断处理函数中不应该有额外的参数

五、中断服务程序的特点

1、特点

1)要求执行速度越快越好

2)Linux的中断处理函数中不应该出现引起阻塞或休眠的函数

如:rec() \ sleep()

3)Linux的中断处理过程应该使用独立的栈(内核态的栈)

4)中断服务程序工作于中断上下文,所以不能和用户空间进行数据交互

copy_to_user

copy_from_user

2、中断上下文

中断发生以后,CPU接收到中断信号,硬件会干4件事儿

1)把CPSR备份到SPSR

2)修改CPSR的一些位

MODE模式

I 禁止IRQ

F 禁止FIQ

T 切换到ARM工作状态

3)保存返回地址到LR寄存器

4)跳转到异常向量表中执行

3、如何去做

Linux操作系统中断上下文越快执行完越好,但对于硬件来说,有些硬件执行起来很慢,针对这种情况,Linux内核提出了底半部机制

1)顶半部(上半部)

只做紧急的工作

如:一些寄存器的读写、中断pending的清除操作、登记底半部

2)底半部(下半部)

做不紧急的工作(但必须要做)

如:sleep() \ delay() \ 读写时序的延时

4、登记底半部

1)软中断

如:sei指令(需要修改内核代码,不能以独立的.ko文件存在,实现起来不方便)

2)tasklet

tasklet是基于软中断方式实现的

使用步骤:

【1】定义tasklet变量

struct tasklet_struct btn_tasklet

【2】初始化tasklet变量

cpp 复制代码
static inline void tasklet_init(struct tasklet_struct *tasklet, void (*func)(unsigned long), unsigned long data)

注释:

tasklet:tasklet变量地址

func:底半部函数的地址

data:底半部函数的参数

【3】使用成功初始化的tasklet登记底半部

static inline void tasklet_schedule(struct tasklet_struct *tasklet)

【4】实验

1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

2】创建新的工程

mkdir tasklet_btn

3】编写程序

vim tasklet_btn.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <mach/platform.h>
#include <linux/interrupt.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

#define IRQ_GPIOA28		(IRQ_GPIO_A_START + 28)
#define IRQ_GPIOB30		(IRQ_GPIO_B_START + 30)
#define IRQ_GPIOB31		(IRQ_GPIO_B_START + 31)
#define IRQ_GPIOB9		(IRQ_GPIO_B_START + 9)

#define BTN_UP			IRQ_GPIOA28
#define BTN_DOWN		IRQ_GPIOB30
#define BTN_LEFT		IRQ_GPIOB31
#define BTN_RIGHT		IRQ_GPIOB9 


// define a struct btn_desc to save the info of irq and name
typedef struct btn_desc {
	int irq;
	char *name;
}btn_desc_t;

btn_desc_t btns[] = {
	{BTN_UP	 , "up"},
	{BTN_DOWN , "down"},
	{BTN_LEFT , "left"},
	{BTN_RIGHT, "right"}
};

// step_1 :define the global tasklet_struct btn_tasklet
struct tasklet_struct btn_tasklet;
// story the counts of press key
int count = 0;

// register the interrupt handler
irqreturn_t btn_irq(int irq, void *data)
{
	btn_desc_t *pdata = (btn_desc_t *)data;

	printk(KERN_EMERG "%s is pressed!\n", pdata->name);

	// step_3 : register the partial of buttom
	tasklet_schedule(&btn_tasklet);

	return IRQ_HANDLED;
}

// step_4 : implement the fun of the partial of buttom
void btn_tasklet_func(unsigned long data)
{
	int *pdata = (int *)data;

	printk(KERN_EMERG "do bottom half work : count = %d\n", *pdata);

	// not delay
    // udelay(100);
    // msleep(100);
	(*pdata)++;

	return ;
}

int __init interrupt_btn_init(void)
{
	int i = 0;
	int ret = 0;	// recive the ret value

	for (i = 0; i < ARRAY_SIZE(btns); i++) {
		ret = request_irq(btns[i].irq, btn_irq, IRQF_TRIGGER_FALLING, btns[i].name, &(btns[i]));
		if (ret) {
			printk(KERN_EMERG "request_irq is failed!\n");
			i --;
			while (i >= 0) {
				// if register failed, we should destory the irq
				free_irq(btns[i].irq, &(btns[i]));
				i --;
			}

			return -EAGAIN;
		}
	}

	// step_2 : init the tasklet_struct variable btn_tasklet
	tasklet_init(&btn_tasklet, btn_tasklet_func, (unsigned long)&count);

	return 0;
}

void __exit interrupt_btn_exit(void)
{
	int i = 0;

	for (i = 0; i < ARRAY_SIZE(btns); i++) {
		free_irq(btns[i].irq, &(btns[i]));
	}

	return ;
}

module_init(interrupt_btn_init);
module_exit(interrupt_btn_exit);

4】编写Makefile

vim Makefile

bash 复制代码
obj-m += tasklet_btn.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/rootfs

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean

5】编译工程

make

6】下位机测试

insmod tasklet_btn.ko(不加延时)

insmod tasklet_btn.ko(加延时)

注意:

使用tasklet登记的底半部函数不能调用有阻塞或者休眠的函数,因为tasklet是基于软件中断的方式实现的,其登记的函数工作于中断的上下文,对其进行阻塞或延时对其它需要中断的函数影响很大。

Linux内核的延时会导致内核吐核,从而使得linux内核崩溃,比较严重

(毫秒级的延时会导致内核吐核,微秒级的延时不会导致内核吐核)

3)任务队列

不同于tasklet,我们可以使用工作队列(FIFO),完成我们对底半部使用有阻塞或休眠函数的需求

使用步骤:

【1】定义工作队列对象

struct work_struct btn_work

【2】初始化tasklet变量

INIT_WORK(_work, _func)

注意:

【3】使用成功初始化的work登记底半部

int schedule_work(struct work_struct *work)

【4】对已经登记但还未执行的work进行处理

void flush_scheduled_work(void)

bool cancel_work_sync(struct work_struct *work)

【5】实验

1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

2】创建新的工程

mkdir work_btn

3】编写程序

vim tasklet_btn.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <mach/platform.h>
#include <linux/interrupt.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

#define IRQ_GPIOA28		(IRQ_GPIO_A_START + 28)
#define IRQ_GPIOB30		(IRQ_GPIO_B_START + 30)
#define IRQ_GPIOB31		(IRQ_GPIO_B_START + 31)
#define IRQ_GPIOB9		(IRQ_GPIO_B_START + 9)

#define BTN_UP			IRQ_GPIOA28
#define BTN_DOWN		IRQ_GPIOB30
#define BTN_LEFT		IRQ_GPIOB31
#define BTN_RIGHT		IRQ_GPIOB9 


// define a struct btn_desc to save the info of irq and name
typedef struct btn_desc {
	int irq;
	char *name;
}btn_desc_t;

btn_desc_t btns[] = {
	{BTN_UP	 , "up"},
	{BTN_DOWN , "down"},
	{BTN_LEFT , "left"},
	{BTN_RIGHT, "right"}
};

// step_1 :define the global work_struct btn_work
struct work_struct btn_work;
// story the counts of press key
int count = 0;

// register the interrupt handler
irqreturn_t btn_irq(int irq, void *data)
{
	btn_desc_t *pdata = (btn_desc_t *)data;

	printk(KERN_EMERG "%s is pressed!\n", pdata->name);

	// step_3 : register the partial of buttom
	schedule_work(&btn_work);

	return IRQ_HANDLED;
}

// step_4 : implement the fun of the partial of buttom
static void btn_work_func(struct work_struct *work)
{

	printk(KERN_EMERG "do bottom half work !\n");

	msleep(10000);

	printk(KERN_EMERG "time is over!\n");

	return ;
}

int __init interrupt_btn_init(void)
{
	int i = 0;
	int ret = 0;	// recive the ret value

	for (i = 0; i < ARRAY_SIZE(btns); i++) {
		ret = request_irq(btns[i].irq, btn_irq, IRQF_TRIGGER_FALLING, btns[i].name, &(btns[i]));
		if (ret) {
			printk(KERN_EMERG "request_irq is failed!\n");
			i --;
			while (i >= 0) {
				// if register failed, we should destory the irq
				free_irq(btns[i].irq, &(btns[i]));
				i --;
			}

			return -EAGAIN;
		}
	}

	// step_2 : init the work_struct variable btn_work
	INIT_WORK(&btn_work, btn_work_func);

	return 0;
}

void __exit interrupt_btn_exit(void)
{
	int i = 0;

	// step_5 : deal the without execute func
	// flush_scheduled_work(&btn_work)
	cancel_work_sync(&btn_work);

	for (i = 0; i < ARRAY_SIZE(btns); i++) {
		free_irq(btns[i].irq, &(btns[i]));
	}

	return ;
}

module_init(interrupt_btn_init);
module_exit(interrupt_btn_exit);

4】编写Makefile

vim Makefile

bash 复制代码
obj-m += work_btn.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/rootfs

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean

5】编译工程

make

6】下位机测试

insmod work_btn.ko

注意:

使用work登记的底半部函数工作于进程的上下文,所以对底半部的调用没有限制,不会影响其它需要进行中断的函数。

内核中维护了一个工作队列,每调用一次schedule_work(),就会在维护的工作队列中添加一个节点,内核专门维护了一个线程对工作队列中的节点进行扫描,挨个执行。

4)比较

【1】使用tasklet登记的底半部函数不能执行阻塞或睡眠的函数

【2】使用工作队列登记的底半部函数可以调用阻塞或睡眠的函数

5)delayed_work

(在底半部登记完成后,延时一段时间后再执行)

delayed_work的原理和工作队列没什么区别

只不过比工作队列多了一个内核定时器(登记完底半部函数之后会计时)

使用步骤:

【1】定义delayed_work

struct delayed_work btn_dwork

【2】初始化tasklet变量

INIT_DELAYED_WORK(_work, _func)

【3】使用成功初始化的work登记底半部

int schedule_delayed_work(struct delayed_work *dwork, unsigned long delay)

【4】对已经登记但还未执行的work进行处理

bool flush_delayed_work(struct delayed_work *dwork)

bool cancel_delayed_work_sync(struct delayed_work *dwork)

【5】实验

1】进入工程目录

cd /home/zjd/s5p6818/KERNEL/drivers

2】创建新的工程

mkdir delayed_btn

3】编写程序

vim delayed_btn.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <mach/platform.h>
#include <linux/interrupt.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

#define IRQ_GPIOA28		(IRQ_GPIO_A_START + 28)
#define IRQ_GPIOB30		(IRQ_GPIO_B_START + 30)
#define IRQ_GPIOB31		(IRQ_GPIO_B_START + 31)
#define IRQ_GPIOB9		(IRQ_GPIO_B_START + 9)

#define BTN_UP			IRQ_GPIOA28
#define BTN_DOWN		IRQ_GPIOB30
#define BTN_LEFT		IRQ_GPIOB31
#define BTN_RIGHT		IRQ_GPIOB9 


// define a struct btn_desc to save the info of irq and name
typedef struct btn_desc {
	int irq;
	char *name;
}btn_desc_t;

btn_desc_t btns[] = {
	{BTN_UP	 , "up"},
	{BTN_DOWN , "down"},
	{BTN_LEFT , "left"},
	{BTN_RIGHT, "right"}
};

// step_1 :define the global delayed_work btn_dwork
struct delayed_work btn_dwork;
// story the counts of press key
int count = 0;

// register the interrupt handler
irqreturn_t btn_irq(int irq, void *data)
{
	btn_desc_t *pdata = (btn_desc_t *)data;

	printk(KERN_EMERG "%s is pressed!\n", pdata->name);

	// step_3 : register the partial of buttom
	schedule_delayed_work(&btn_dwork, 10 * HZ);    // delay 10s to execute the func

	return IRQ_HANDLED;
}

// step_4 : implement the fun of the partial of buttom
static void btn_dwork_func(struct work_struct *work)
{

	printk(KERN_EMERG "do bottom half work !\n");

	return ;
}

int __init interrupt_btn_init(void)
{
	int i = 0;
	int ret = 0;	// recive the ret value

	for (i = 0; i < ARRAY_SIZE(btns); i++) {
		ret = request_irq(btns[i].irq, btn_irq, IRQF_TRIGGER_FALLING, btns[i].name, &(btns[i]));
		if (ret) {
			printk(KERN_EMERG "request_irq is failed!\n");
			i --;
			while (i >= 0) {
				// if register failed, we should destory the irq
				free_irq(btns[i].irq, &(btns[i]));
				i --;
			}

			return -EAGAIN;
		}
	}

	// step_2 : init the delayed_work variable btn_dwork
	INIT_DELAYED_WORK(&btn_dwork, btn_dwork_func);

	return 0;
}

void __exit interrupt_btn_exit(void)
{
	int i = 0;

	// step_5 : deal the without execute func
	flush_delayed_work(&btn_dwork);
	//cancel_delayed_work_sync(&btn_dwork);

	for (i = 0; i < ARRAY_SIZE(btns); i++) {
		free_irq(btns[i].irq, &(btns[i]));
	}

	return ;
}

module_init(interrupt_btn_init);
module_exit(interrupt_btn_exit);

4】编写Makefile

vim Makefile

bash 复制代码
obj-m += delayed_btn.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/rootfs

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean

5】编译工程

make

6】下位机测试

insmod delayed_btn.ko

【6】总结

【a】注册多次底半部函数,只有第一次有效

【b】登记底半部函数之后,如果使用的是flush销毁,我们不等函数执行,就将模块卸载,会使得底半部函数提前执行

相关推荐
东软吴彦祖7 分钟前
将已有的MySQL8.0单机架构变成主从复制架构
服务器·mysql·架构
2401_8812780817 分钟前
搭建 PostgreSQL 主从架构
运维
 嘘 23 分钟前
Centos使用Mysql
linux·mysql·centos
 嘘 25 分钟前
Centos使用人大金仓ksql
linux·运维·centos·人大金仓
路由侠内网穿透30 分钟前
外网访问 WebDav 服务
大数据·服务器
?crying41 分钟前
深入理解 source 和 sh、bash 的区别
linux·开发语言·bash
是阿建吖!1 小时前
【Linux】操作系统
linux·运维·服务器
一只小菜鸡..1 小时前
241114.学习日志——[CSDIY] [CS]数据结构与算法 [00]
linux·服务器·学习
水饺编程1 小时前
【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-24
linux·嵌入式硬件·fpga开发·硬件架构
程序猿ZhangSir1 小时前
Linux系统Centos设置开机默认root用户
linux·运维·centos