自学内容网 自学内容网

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

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_MAGOR200
#define CHRDEV_MINOR26
#define CHRDEV_NUM1
#define CHRDEV_NAME"myleds"
#define HIGH1
#define LOW0
#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_ONLOW
#define TURN_OFFHIGH



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


原文地址:https://blog.csdn.net/qq_45882170/article/details/141438166

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!