Linux_kernel中断系统13

news2024/11/24 12:09:23

一、温故知新

        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目录下创建与逻辑类对应的设备文件。

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

#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

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

#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

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)修改下位机环境变量

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)中断注册函数
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

#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

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变量

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

#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

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

#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

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

#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

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销毁,我们不等函数执行,就将模块卸载,会使得底半部函数提前执行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2205613.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

闪迪U盘误删的数据该怎么恢复呢?3个方法轻松解决

闪迪是一家全球知名的美国公司&#xff0c;也是全球最大的闪存数据存储卡产品供应商&#xff0c;其中&#xff0c;闪迪U盘作为其主要产品之一&#xff0c;因其便携性、大容量和高速传输能力而深受用户喜爱。然而&#xff0c;在平时存储重要数据的时候&#xff0c;会因为我们一系…

Ngin入门套餐

快速了解Nginx 一、代理1.1 正向代理1.2 反向代理1.3 正向代理和反向代理的区别 二、Nginx负载均衡策略2.1 轮询&#xff08;Round Robin&#xff09;2.2 加权轮询&#xff08;Weighted Round Robin&#xff09;2.3 IP 哈希&#xff08;IP Hash&#xff09;2.4 最少连接&#x…

C语言 | Leetcode C语言题解之第462题最小操作次数使数组元素相等II

题目&#xff1a; 题解&#xff1a; static inline void swap(int *a, int *b) {int c *a;*a *b;*b c; }static inline int partition(int *nums, int left, int right) {int x nums[right], i left - 1;for (int j left; j < right; j) {if (nums[j] < x) {swap(…

Spring17——Spring事务简介、使用事务实现模拟转账业务

38-Spring事务简介 相关概念 事务作用&#xff1a;在数据层保障一系列的数据库操作同成功同失败Spring事务作用&#xff1a;在数据层或业务层保障一系列的数据库操作同时成功或同时失败 数据层有事务我们可以理解&#xff0c;为什么业务层也需要处理事务呢&#xff1f;举个简…

黑马点评(更新中)

黑马点评 1、短信登录 Session实现1.1 分析1.2、Session实现的缺点1.3、其中的问题1.3.1、session覆盖1.3.2、在拦截之后remove User的作用 2、用Redis实现短信登录2.1 分析2.2 代码以及问题2.2.1 String问题2.2.2 刷新问题2.2.3 注入对象问题2.2.4 拦截器order问题 3、缓存3.1…

6个设计师都在用的样机素材网站

设计师都在哪里找样机素材&#xff1f;推荐6个样机素材网站&#xff0c;免费下载&#xff0c;赶紧收藏好&#xff01; 1、菜鸟图库 样机图片素材-样机图片模板免费下载 - 菜鸟图库 菜鸟图库有多种类型的设计素材&#xff0c;像平面、电商、UI、办公等素材这里面都能找到&#…

若依-二级页面的跳转设计

配置二级路由页面&#xff0c;就是点击了某个按钮之后会跳转到一个页面中去&#xff0c;比如需要点击查看详情的时候就可以进行页面的跳转。 点击字典类型 进入到字典数据页面。这个页面在左侧是没有的&#xff0c;在导航栏会有展示出来。 在index.js中配置的代码 //path 当前的…

从新手到专家,Tableau Agent 如何满足不同用户的分析需求?

为什么要构建 Tableau Agent&#xff1f;Tableau 对话式 AI 助理如何助你加速分析&#xff1f; 正在查询相关数据和现有仪表板&#xff0c;看看能否解答业务问题&#xff1f; 还在持续准备、管理和编辑数据&#xff0c;以确保企业数据的质量和可访问性&#xff1f; 希望快速创…

SDH8323非隔离12V/15V/18V,300MA直插DIP7电源芯片

SDH8323 是高压启动&#xff0c;内置高压MOSFET的电流模式PWMPFM控制器&#xff0c;适用于Buck及Buck-Boost拓扑。 SDH8323 在轻载条件下降频工作&#xff0c;优化轻载条件下的转换效率。在极轻载及空载条件下工作于打嗝模式&#xff0c;从而有效地降低系统的待机功耗。 SDH832…

DIFY上使用多种大语言模型(MindCraft API)

注册MindCraft并创建API KEY 首先我们在智匠MindCraft上注册账号并创建API KEY&#xff0c;参考接口调用文档&#xff0c;查看我们能调用哪些模型。我们可以看到这个开发平台上整合了主流的大语言模型&#xff0c;并且是兼容openai接口的。 进入DIFY的设置界面 然后我们在DIFY上…

为何一个简单的线上商城 两年销售7000多万?

有一个销售百货的商城&#xff0c;他们的返现模式极为独特且富有吸引力。无论你消费多少&#xff0c;商城都会随机给你返还一定金额的钱&#xff0c;这个返还范围从10元到1000元不等&#xff0c;确实非常随意。 那么&#xff0c;这个商城采用如此随性的返现模式&#xff0c;业绩…

软件性能测试有哪些测试指标?性能测试第三方软件测评中心推荐

作为衡量软件质量的重要指标之一&#xff0c;软件的性能是一种非功能特性&#xff0c;不关心系统是否可以完成特定的功能&#xff0c;而只关心软件系统在运行时的速度是否足够快、是否消耗足够少的资源&#xff0c;因此软件性能测试至关重要。性能测试是指软件测试人员根据产品…

监控台操作台在哪些企业中应用比较广泛

在现代企业管理中&#xff0c;监控台操作台作为一种集成了视频监控、音频监听、数据分析及远程控制等多种功能的综合性操作平台&#xff0c;正逐渐成为众多企业不可或缺的重要设备。其广泛的应用领域不仅提升了企业的运营效率&#xff0c;还极大地增强了企业的安全性能。 一、安…

淘宝程序员没活硬整?在 Excel 和 VSCode 中购物!

大家好&#xff0c;我是程序员鱼皮&#xff0c;最近某宝网站的改进&#xff0c;属实是有点 “新” 了。 你敢相信这是一个购物网站么&#xff1f; 你可以在 Excel 表格中挑选商品进行购物&#xff0c;还原度极高&#xff0c;这两个图表更是点睛之笔。哪个天才想出来的&#xf…

C++Linux项目推荐-Web多人聊天+MySQL+Redis+Websocket+Json,可以写简历的C++项目

1 项目地址 项目配套视频简介&#xff1a;程序员老廖的个人空间-程序员老廖个人主页-哔哩哔哩视频 (bilibili.com) 1.1 项目原有功能 https://github.com/anarthal/servertech-chat.git 功能&#xff1a; 支持HTTP请求&#xff0c;掌握HTTP API json的请求相应 支持Webso…

如何使用ssm实现疫情居家办公OA系统

TOC 10902ssm疫情居家办公OA系统 系统概述 进过系统的分析后&#xff0c;就开始记性系统的设计&#xff0c;系统设计包含总体设计和详细设计。总体设计只是一个大体的设计&#xff0c;经过了总体设计&#xff0c;我们能够划分出系统的一些东西&#xff0c;例如文件、文档、数…

手机怎么玩荒野大镖客2?GameViewer远程助你手机随时随地畅玩大表哥2

手机免费玩电脑游戏&#xff0c;原来手机也能随时随地玩荒野大镖客2&#xff1f;如果你想你手机随时随地畅玩大表哥2&#xff0c;可以使用网易GameViewer远程来实现。 GameViewer远程4K蓝光144帧的高画质&#xff0c;可以让你在玩荒野大镖客2时&#xff0c;体验到开放大世界的所…

2024年11月软考准考证什么时候打印?

打印时间一览表&#xff1a; 北京&#xff1a;2024年11月5日至11月8日 上海&#xff1a;2024年11月6日10:00至11月8日16:00 天津&#xff1a;2024年11月5日9:00后 重庆&#xff1a;2024年11月4日9:00至10日9:05 广东&#xff1a;2024年11月5日9:00至8日17:00 深圳&#xf…

靠“代工+营销”支撑,又突击分红10亿元,毛戈平冲刺上市为哪般

撰稿|行星 来源|贝多财经 10月9日&#xff0c;毛戈平化妆品股份有限公司&#xff08;下称“毛戈平”或“毛戈平公司”&#xff09;在港交所更新招股书&#xff0c;继续推进港股上市进程。此前&#xff0c;毛戈平曾于4月8日向港交所递表&#xff0c;但因财务资料已过有效期而“…

selenium:Select类操作复选框和下拉框(7)

复选框/下拉框操作的Select类 主要使用selinium中的类Select来模拟选择网页上的下拉框或者复选框中的内容&#xff0c;使用前先导入 from selenium.webdriver.support.ui import Select 主要方法如下&#xff1a; 函数 功能 select_by_value 根据复选框/下拉框的值选择 se…