• 嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM驱动编程第六天-ARM Linux编程之高级驱动基础 (物联技术666)


    链接:https://pan.baidu.com/s/1V0E9IHSoLbpiWJsncmFgdA?pwd=1688
    提取码:1688

    教学内容:

    1、内核中断

    Linux操作系统下同裸机程序一样,需要利用中断机制来处理硬件的异步事件,但用户态不允许中断事件,因此中断必须由设备驱动程序来接收与处理,如果CPU接收到一个中断,它会停止一切工作,调用中断处理函数,此时进程调度也会停止,所以就要求我们的中断处理一定要快。

    中断处理程序运行在中断上下文中,注意事项如下:

    • 中断处理程序不能停止运行
    • 不能使用导致睡眠的处理机制(互斥锁、信号量等),用自旋锁代替
    • 中断处理函数不能与用户空间直接交互数据
    • 中断处理函数不必是可重用的
    • 中断处理函数可以被嵌套(不同优先级之间)
    • 中断处理函数执行时间尽可能短,中断处理通常分两个部分:

    顶半部:为下半部做准备,并表示已经服务了此中断(相当于登记中断)

    底半部:完成重大工作负载,执行过程中所有中断都是使能的(具体处理中断)

           对于,底半部的中断处理,为了不占用CPU太多时间,可以采用队列和微线程的方式处理底半部中断程序,顶半部的登记工作一般在__init或open函数中完成。

    #include          //申请中断

    int request_irq(unsigned int irq,

             irqreturn_t(*handler)(int,void*),

             unsigned long irqflag,

             const char *devname, void *dev_id);

    参数1:中断号,所申请的中断向量,比如EXIT0中断等并定义在mach/irqs.h

    参数2:中断服务程序

    参数3:中断属性设置,定义在linux/interrupt.h

        IRQF_DISABLED,    用于保证中断不被打断和嵌套(值为0)

        IRQF_SHARED,       申请子中断时,共享中断源

        IRQF_SAMPLE_RANDOM, 中断可能被用来产生随机数

    参数4:中断名字,cat /proc/interrupts 可察看系统中断申请与使用情况

    参数5:中断参数,作为共享中断时用于区别不同中断的参数irqreturn_t的第二个参数

    返回0为成功,失败返回-INVAL或-EBUSY

    void free_irq(unsigned int irq, void *dev_id);      // 释放中断

    参数1:所要清除中断服务程序的中断号

    参数2:中断参数,同申请时的值一致

    void enable_irq(unsigned int irq);    // 使能中断

    参数:中断号

    void disable_irq(unsigned int irq);    // 关闭中断,并等待中断处理完成后返回

    参数:中断号

    void disable_irq_nosync(unsigned int irq);       // 关闭中断,立即返回

    参数:中断号

    注意:当用户在中断处理函数中关闭中断时应当使用disable_irq_nosync,否则会形成死锁

    如果中断程序比较短,就不必要把中断分为二个部分,

    对于中断的设置,可以使用

    #include

    int set_irq_type(int irq, int edge);                

     参数1:中断号   #include

     参数2:外部中断触发方式定义在linux/irq.h

        IRQ_TYPE_LEVEL_LOW,

        IRQ_TYPE_LEVEL_HIGH,

        IRQ_TYPE_EDGE_FALLING,

        IRQ_TYPE_EDGE_RISING,

        IRQ_TYPE_EDGE_BOTH

    具体程序如下:

    //**************************************************

    #include    /*module_init()*/

    #include /* printk() */

    #include             /* __init __exit */

    #include         /* file_operation */

    #include       /* copy_to_user, copy_from_user */

    #include          /*class ,class_create ,device_create 等*/

    #include   /* Error number */

    #include   /* udelay */

    #include   /*S3C2410_GPGCON ,S3C2410_GPIO_IRQ*/

    #include              /*S3C24XX_VA_GPIO*/

    #include //set_irq_type ,IRQ_TYPE_EDGE_FALLING

    #include        //request_irq , free_irq

    #include     //IRQ_EINT2

    #include   //s3c2410_gpio_cfgpin();

    #define DRIVER_NAME     "key13_eint"

    static int MAJOR_NR = 0;         /* Driver Major Number */

    static int MINOR_NR = 0;         //次设备起始号

    struct class *my_class;     

    static struct semaphore readable;   //信息号,阻塞read调用,后续知识

    static unsigned char key;

          

    static irqreturn_t buttons_interrupt(int irq, void *dev_id) //和request_irq第二参数对应

    {

           key = (unsigned int)dev_id;

           up(&readable);

               return 0;

    }

    static int keyDriver_open(struct inode *inode, struct file *file)

    {return 0;}

    static int keyDriver_release(struct inode *inode, struct file *file)

    { return 0;}

    static int keyDriver_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)

    {

           int ret = 0;

           if(down_interruptible(&readable) != 0){return 0;}

           ret = copy_to_user(buff, &key, sizeof(key));

           if(ret != 0){

                 PRINTK("in function read,copy_to_user function run failed!\n");

                 return ret;

           }

           return sizeof(key);

    }

    static struct file_operations keyDriver_fops = {

           .owner = THIS_MODULE,

           .read = keyDriver_read,

           .open = keyDriver_open,

           .release = keyDriver_release,

    };

    static int __init myModule_init(void)

    {

           int ret;

           /* Module init code */

           PRINTK("keyDriver_init\n");

          

           sema_init(&readable, 0);          //信号量初始化

          

           ret = set_irq_type(IRQ_EINT2,IRQ_TYPE_EDGE_FALLING);//设置外部中断

           if(ret != 0){

                 PRINTK("IRQ_EINT2 set irq type failed!\n");

                 return ret;

           }

           ret = set_irq_type(IRQ_EINT3,IRQ_TYPE_EDGE_FALLING);

           if(ret != 0){

                 PRINTK("IRQ_EINT3 set irq type failed!\n");

                 return ret;

           }

           ret = set_irq_type(IRQ_EINT4,IRQ_TYPE_EDGE_FALLING);

           if(ret != 0){

                 PRINTK("IRQ_EINT4 set irq type failed!\n");

                 return ret;

           }

           ret = request_irq(IRQ_EINT2,buttons_interrupt, IRQF_DISABLED,"KEY1", (void *)1);

           ret = request_irq(IRQ_EINT3,buttons_interrupt, 0,"KEY2", (void *)2);

           ret = request_irq(IRQ_EINT4,buttons_interrupt, 0,"KEY3", (void *)3);

           MAJOR_NR = register_chrdev(MAJOR_NR, DRIVER_NAME, &keyDriver_fops);

           if(MAJOR_NR < 0)

           {

                 PRINTK("register char device fail!\n");

                 return MAJOR_NR;

           }

    my_class=class_create(THIS_MODULE,"udev_key13_eint");

           device_create(my_class,NULL, MKDEV(MAJOR_NR, MINOR_NR), NULL,DRIVER_NAME);

          

           PRINTK("register myDriver OK! Major = %d\n", MAJOR_NR);

           return 0;

    }

    static void __exit myModule_exit(void)

    {

           PRINTK("exit in\n");

          

           free_irq(IRQ_EINT2, (void *)1);

           free_irq(IRQ_EINT3, (void *)2);

           free_irq(IRQ_EINT4, (void *)3);

           if(MAJOR_NR > 0)

           {

                 unregister_chrdev(MAJOR_NR, DRIVER_NAME);

           device_destroy(my_class,MKDEV(MAJOR_NR, MINOR_NR));

                 class_destroy(my_class);

                 PRINTK("myModule_exit ok\n");

           }

           return;

    }

    module_init(myModule_init);

    module_exit(myModule_exit);

    MODULE_LICENSE("GPL");

    MODULE_AUTHOR("HB");

    //**************************************************

    如果中断处理程序比较大,可以使用上下文分开的形式,对于下文的执行,可以使用队列或者微线程方式。

    2、内核微线程:(参考7、嵌入式linux设备驱动\02、字符设备驱动\somecode\1_basic_cdriver\2_advanced_cdriver\1_interrupt\1_key13_eint

    创建方法一:

    //直接利用宏直接创建

    DECLARE_TASKLET(name, func, data);

    参数分别为:微线程名称、任务处理函数、任务处理函数的参数

    //微线程调度函数

    void tasklet_schedule(struct tasklet_struct * );

    函数功能:将tasklet加入tasklet_(hi_)vec后,激发软件中断,并立即返回

    //微线程销毁函数

    void tasklet_kill(struct tasklet_struct * );

    //*****************************************

    #include

    void tasklet_function( unsigned long data );//声明已定义的任务处理函数

    DECLARE_TASKLET(my_tasklet, tasklet_function, tasklet_data);

    tasklet_schedule(&my_tasklet);       //中断处理程序中调用

     ......

    //使用完毕后,调用以下函数销毁已初始化的微线程

    tasklet_kill(&key_tasklet);

    //*****************************************

    创建方法二:

    //微线程初始化函数

    void tasklet_init(struct tasklet_struct *t,

                   void (*func)(unsigned long), unsigned long data);

    参数1:用户已定义tasklet_struct 变量的地址

    参数2:tasklet的任务处理函数

    参数3:func函数的参数

    //微线程调度函数

    void tasklet_schedule(struct tasklet_struct * );

    函数功能:将tasklet加入tasklet_(hi_)vec后,激发软件中断,并立即返回

    //微线程销毁函数

    void tasklet_kill(struct tasklet_struct * );

    //**************

    其他的功能:

    微线程禁止、打开、激发软中断:

    //禁止微线程调度,其中tasklet_disable_nosync( )函数和disable_irq_nosync( )类似

    void tasklet_disable(struct tasklet_struct * );       

    void tasklet_disable_nosync(struct tasklet_struct * );

    //允许微线程调度

    void tasklet_enable(struct tasklet_struct * );

    3、工作队列:

    对于队列方式可以实现阻塞进程的唤醒,具体队列程序如下:

    1)、定义工作结构体和自定义队列结构体

    static struct workqueue_struct *key_workqueue;

    static struct work_struct key_work;

    //static struct workqueue_struct *key_workqueue;

    //struct delayed_work key_work;

    2)、在open或者__init中创建队列和初始化任务

    key_workqueue = create_workqueue("key_queue");

    INIT_WORK(&key_work,keyScan_Handler);

    //key_workqueue = create_workqueue("key_queue");

    //INIT_DELAYED_WORK(&dwork, func);

    3)、在中断(适当的地址)中把任务添加到队列中去

    queue_work(key_workqueue,&key_work);

    //int queue_delayed_work(key_workqueue,&key_work,unsigned long delay);

    4)、在退出时候,把队列任务清空,销毁队列(在__exit)

    flush_workqueue(key_workqueue);

    destroy_workqueue(key_workqueue);

    注意:在队列中也可以不使用自定义队列,使用全局内核队列,这样就不需要定义、创建、清空、销毁等动作,只需把任务添加全局队列即可;绿色的为带延时的队列。

    //添加任务到内核全局工作队列

    int schedule_work(struct work_struct *work);

    参数:已初始化的工作

    4、内核并发处理

           当多个进程、线程或中断、正常用户程序同时访问同一个资源,可能导致错误,因此内核需要提供并发控制机制,对公共资源的访问进行同步控制,确保共享资源的安全访问。

           一般linux中包含了众多的互斥与同步机制,包括信号量、互斥体、自旋锁、原子操作、读写锁等来实现并发机制的实现。

    情况一、单核处理器

           对于单核处理器来说,进程只是在宏观上并行,而在微观上是串行的,所以对于一个公共区域操作,几个进程之间对区域操作不存在竞发状态,只要防止在进程对区域读写的时候,不被打断即可,为此分二种情况,一种是抢占性内核、即高优先级进程能够打断低优先级进程,另一种是非抢占性内核,只有中断才可能打断进程;所以在抢占性单核情况要使用自旋锁和关中断,在非抢占内核情况下仅仅只要关中断。

    情况二、多核处理器(SMP系统)

    对于上述单核的情况只能防止一个处理器的竞发,如果多核的情况就不能防止了,为此,只能在临界区段(即公共区域)加上自旋锁。

    手段一:中断屏蔽

    Local_irq_disable();    //屏蔽所有中断

    ...... //临界区代码

    Local_irq_enable();           //开放所有中断

    注:使用该函数有时会出现问题,如果在屏蔽中断前,中断已被关闭,那么在开放中断时之前默认处于关闭状态的中断被打开。

    我们可以通过下面的方法避免这种情况:

    Local_irq_save();        //保存中断状态,关闭所有中断

    ...... //临界区代码

    Local_irq_restore();    //打开中断,恢复关闭前中断状态

    手段二:自旋锁

    1)、定义自旋锁

    static struct spinlock my_spin_lock;

    //static spinlock_t my_spin_lock;

    2)、初始化自旋锁

    spin_lock_init(&my_spin_lock);

    3)、在程序区加锁,离开解锁,加解锁有四种形式

    //获得自旋锁,(可自旋等待,可被软、硬件中断)

    void spin_lock(spinlock_t *my_spinlock);

    //获得自旋锁,(可自旋等待,保存中断状态并关闭软、硬件中断)

    void spin_lock_irqsave(spinlock_t *my_spinlock, unsigned long flags);

    //获得自旋锁,(可自旋等待,不保存中断状态关闭软、硬件中断)

    void spin_lock_irq(spinlock_t *my_spinlock);

    //获得自旋锁,(可自旋等待,不保存中断状态关闭硬件中断)

    void spin_lock_bh(spinlock_t *lock);

    注意:进程在获取自旋锁失败自旋等待时都不可以被系统消息打断

    /释放自旋锁,退出临界区,严格与上面加锁对应

    void spin_unlock(spinlock_t *lock)

    void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)

    void spin_unlock_irq(spinlock_t *lock)

    void spin_unlock_bh(spinlock_t *lock)

    //尝试获得自旋锁,(不自旋等待,成功返回1、失败则返回0)

    int spin_trylock(spinlock_t *lock)

    //*************************************

    #include

    spinlock_t my_spinlock;

    spin_lock_init(&my_spinlock);

    //或者spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;

    spin_lock(&my_spinlock);

     .......    //临界区代码

    spin_unlock(&my_spinlock);

    //***************************************

    手段三:互斥和信号量(和linux系统编程类似)

    1)、定义信号量

    struct semaphore sem;

    2)、初始化信号量

    void sema_init(struct semaphore *sem, int val);

    val:信号值

    3)、加或减信号量

    // 获取(等待)信号量,(减操作,不能被系统消息打断,会导致调用者睡眠)

    void down(struct semaphore *sem);

    // 获取(等待)信号量,(减操作,可以被系统消息打断,会导致调用者睡眠)

    int down_interruptible(struct semaphore *sem);

    //尝试获得信号量,成功返回0,失败返回非0,不会导致调用者睡眠

    int down_trylock(struct semaphore *sem);

    // 释放信号量,即使信号量加1(如果线程睡眠,将其唤醒)

    void up(struct semaphore *sem);

    //************************************

    #include

    struct semaphore my_sema;    //信号量

    sema_init(&my_sema, 1); //信号量初始化为1,与init_MUTEX(my_sema)等效

    //定义一个semaphore变量,并初始化为1

    //DECLARE_MUTEX(my_sema);

    down_interruptible(&my_sema);     //获取信号量(减操作)

     .....      //临界区代码

    up(&bufflock);           //释放信号量(加操作)

    //**************************************

    1)、互斥锁定义和初始化

    struct mutex mut;

    mutex_init(&mut);

    //DEFINE_MUTEX(mutexname) //一步定义和初始化

    2)、加、解锁

    //获得互斥体(加锁),(不能被系统消息打断,若获取该锁失败则进入睡眠)

    extern void mutex_lock(struct mutex *lock);

    //获得互斥体(加锁),(可以被系统消息打断,若获取该锁失败则进入睡眠)

    extern int mutex_lock_interruptible(struct mutex *lock);

    //尝试获得互斥体(加锁),成功返回0,失败返回错误值

    extern int mutex_trylock(struct mutex *lock);

    //释放互斥体(解锁)

    extern void mutex_unlock(struct mutex *lock);

    //***********************************

    #include

    struct mutex my_mutex;

    mutex_init(&my_mutex);

    //或者static DEFINE_MUTEX(my_mutex);

    mutex_lock(&my_mutex);        //获取信号量(减操作)

     .....      //临界区代码

    mutex_unlock(&my_mutex);    //释放信号量(加操作)

    //***************************************

    5、延时与内核定时器

           Linux系统定时器能以可编程的频率中断处理器,此频率为每秒的定时器节拍数,对应内核变量HZ,即1秒中产生的节拍数。

           每当系统定时器中断产生,内部计数器值就加1,此计数器是一个64位的变量"jiffies_64",开发者常使用unsigned long型的变量"jiffies",此或者和jiffies_64相同或者为jiffies_64的低32位(jiffies和jiffies_64相同与否取决于平台),该变量记录了自上次操作系统引导以来的时钟滴答数。

    内核定时器:

    #include

    // 定时器结构体类型

    struct timer_list    

    {

         struct list_head entry;

            //用来形成链表,由内核管理,申请一个定时器,自动加入链表

         unsigned long expires;

            //定时器到期时间(指定一个时刻,即, jiffies计数值) 

         void (*function)(unsigned long);

            // 定时器处理函数

             unsigned long data;   

            // 作为参数被传入定时器处理函数

             ......

    };

    // 初始化定时器

    void init_timer(struct timer_list *timer);

    //添加定时器。定时器开始计时

    void add_timer(struct timer_list * timer);

    //删除定时器,在定时器到期前禁止一个已注册定时器

    int del_timer(struct timer_list * timer);

    //如果定时器函数正在执行则在函数执行完后返回,其它情况同上

    int del_timer_sync(struct timer_list *timer);

    //更新定时器到期时间,并开启定时器

    int mod_timer(struct timer_list *timer, unsigned long expires);

    //查看定时器是否正在被调度运行//return value: 1 if the timer is pending, 0 if not.
    int timer_pending(const struct timer_list *timer);

    设计步骤:

    1)、创建struct timer_list结构体,并且把赋值定时时间,函数入口,函数参数等。

    struct timer_list my_timer;

    my_timer.data = 0L;

    my_timer.function = timer_handler;

    my_timer.expires = jiffies + 1*HZ;

    2)、初始化定时器

    init_timer(&my_timer);

    3)、启动定时

    add_timer(&my_timer);

    4)、如需重复执行, 需要重新初始化并启动定时器

    my_timer.expires = jiffies + 1 * HZ/n;       //1/n秒定时

    add_timer(&my_timer);

    //mod_timer(&my_timer, jiffies+1*HZ);

    5)、关闭或者卸载的时候把定时器销毁

    del_timer_sync(&my_timer);

    //**************************************

    struct timer_list myTimer;

    init_timer(&myTimer);

    myTimer.expires = jiffies + 3 * HZ;

    myTimer.data = 0L;

    myTimer.function = timerHandler;

    add_timer(&myTimer);             //添加定时器到内核

    // 定时器处理函数,计数次数 n = t*HZ(t单位为秒)

    void timerHandler(unsigned long data)

    {

        // 如需重复执行, 需要重新初始化并启动定时器

        myTimer.expires = jiffies + 3 * HZ;

        add_timer(&myTimer);

    }

    ......

    del_timer_sync(&myTimer);     //使用完毕删除定时器

    //*********************************************

    内核延时:

    内核延时分为,等待延时和睡眠延时,对于等待延时很短,在延时期间会一直忙等待(即占据CPU);另一种如果延时较长,进程会进入睡眠延时(即让出CPU)

    短延迟-不依赖于时钟滴答,与具体的CPU类型相关

    #include

    void ndelay(unsigned long nsecs);     //纳秒级延时

    void udelay(unsigned long usecs);     //微秒级延时

    void mdelay(unsigned long msecs);     //毫秒级延时

    注:这三个延时函数使任务进入忙等待,一般不太长的时间可以用它

    长延时-长于一个时钟滴答,为了提高系统运行效率,当前进程需要进入睡眠,让出处理器。

    void msleep(unsigned int millisecs);

    unsigned long msleep_interruptible(unsigned int millisecs);

    void ssleep(unsigned int seconds);

    以上三个函数本质都是依赖于等待队列来实现进程睡眠,让出处理器的。

    队列实现一:(自建队列)

    #include

    long wait_event_timeout(wait_queue_head_t q, condition,long timeout);

    long wait_event_interruptible_timeout(wait_queue_head_t q, condition,long timeout);

    /*这些函数在给定队列上睡眠, 但是它们在超时(以 jiffies 表示)到后返回。如果超时,函数返回 0; 如果这个进程被其他事件唤醒,则返回以 jiffies 表示的剩余的延迟实现;返回值从不会是负值*/

    //*****************

    #include

    wait_queue_head_t wait;

    init_waitqueue_head(&wait);

    wait_event_interruptible_timeout(wait,0,delay);

    //********************

    队列实现二:

    #include

    signed long schedule_timeout(signed long timeout);

    注:等待队列wait_event_timeout函数内部也是通过该函数实现,使用schedule_timeout可以避免声明和使用多余的等待队列头

    /*timeout 是要延时的 jiffies 数。除非这个函数在给定的 timeout 流失前返回,否则返回值是 0 。schedule_timeout 要求调用者首先设置当前的进程状态。为获得一个不可中断的延迟, 可使用 TASK_UNINTERRUPTIBLE 代替。如果你忘记改变当前进程的状态, 调用 schedule_time 如同调用 shcedule,建立一个不用的定时器。*/

    使用实例:

    //*************************

    #include

    ......

    long delay = 100;

    set_current_state(TASK_INTERRUPTIBLE);

    schedule_timeout(delay);

    .....

    //*************************

    set_current_state为设置当前进程的状态,调度器在定时器到时且进程状态变为TASK_RUNNING时才运行该进程,若实现不可中断的延迟可用TASK_UNINTERRUPTIBLE。若使用前忘记改变进程的状态,则定时器不起作用,不能实现延迟

    6Linux驱动阻塞与非阻塞

           阻塞操作,是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作条件后再进行各项操作,实现阻塞一般使用等待队列或信号量的方法。

           非阻塞,进程在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到可以进行操作为止,对于非阻塞而言,如果要知道能不能操作,就必须不断的查询,一般使用轮询操作。

    阻塞实现:

           等待队列在Linux内核中用来阻塞或唤醒一个进程,也可以用来同步对系统资源的访问,还可以实现延迟功能。

    实现方法1---信号量:

    1)、定义信号并初始化

    static struct semaphore readable;

    sema_init(&readable, 0);                //信号量初始化为0

    2)、在需要阻塞的系统调用中,使用down操作(假设read阻塞)

    if(down_interruptible(&readable) != 0)                       //read阻塞实现

           return 0;

    3)、在把数据放入之后,能够让用户层能读取到字符的地方,up操作(假设在中断中)

    up(&readable);

    注:down_interruptible会在没信号量的情况下,阻塞,而睡眠。

    实现方法2--等待队列:

    单任务情况:

    1)、定义和初始化等待队列头

    //定义一个等待队列头

    wait_queue_head_t my_queue;

    //初始一个等待队列头

    init_waitqueue_head(&my_queue);

    //定义并初始化一个等待队列头

    //DECLARE_WAIT_QUEUE_HEAD(my_queue);

    2)、定义一个条件flag,非0为真,0为假;依据条件把当前进程加入队列

    static int flag = 0;

    wait_event_interruptible(my_queue,flag);

    3)、在需要的地址唤醒进程

    flag = 1;

    wake_up_interruptible(&my_queue);

    //*******************************

    #include

    //定义等待队列头

    static DECLARE_WAIT_QUEUE_HEAD(my_queue);

    static int flag = 0;

    .....

    //判断flag条件如果为假,将当前进程推入等待队列并将其睡眠

    wait_event_interruptible(my_queue,flag);

    flag = 0;

    .....

    void func() 

    {

       flag = 1;

       wake_up_interruptible(&my_queue);

    }

    //******************************

    1)、定义和初始化等待队列头

    static DECLARE_WAIT_QUEUE_HEAD(key_waitqueue_head);

    2)、将当前进程推入等待队列将其睡眠

    interruptible_sleep_on(&key_waitqueue_head);

    3)、在需要的地址唤醒进程

    wake_up_interruptible(&key_waitqueue_head);

    非阻塞实现:

           select()函数是提供给用户的接口函数,该函数通过系统调用最终会引发设备驱动中的poll()函数被执行,该机制可以实现一个用户进程对多个设备驱动文件的监测,在设备驱动中的轮询编程poll()函数,为file_operation成员之一。

    轮询方法:

    #include

    //在应用程序中调用的文件描述符监测函数

    int select(int numfds, fd_set *readfds,

             fd_set *writefds, fd_set *exceptfds,

             struct timeval *timeout);

    参数numfds:待监听中最大描述符值加一

    参数readfds:监听读操作的文件描述符集合

    参数writefds:监听写操作的文件描述符集合

    参数exceptfds:监听异常处理的文件描述符集合

    参数timeout:监听等待超时退出select()

    返回值 如果参数timeout设为NULL则表示select()没有timeout。

    错误代码 执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。

    //文件描述符集合的变量的定义      

    fd_set  fds;

    //清空描述符集合

    FD_ZERO(fd_set *set);

    //加入一个文件描述符到集合中

    FD_SET(int fd, fd_set *set);

    //从集合中清除一个文件描述符

    FD_CLR(int fd, fd_set *set);

    //判断文件描述符是否被置位

    FD_ISSET(int fd, fd_set *set);

    返回非0,表示置位(该文件描述集合中有文件可进行读写操作,或产生错误)

    //添加等待队列到wait参数指定的轮询列表中

    void poll_wait(struct file *filp, wait_queue_heat_t *wq,poll_table *wait);

    程序设计步骤:

    1)、定义和初始化头队列

    static DECLARE_WAIT_QUEUE_HEAD(key_waitq);

    2)、在file_operations中添加poll函数

    .poll = keyDriver_poll,

    3)、在poll函数中把添加等待队列到wait参数指定的轮询列表

    poll_wait(file, &key_waitq, wait);

    4)、在中断等需要地方,唤醒等待

    wake_up_interruptible(&key_waitq);

    7Linux内核线程

           内核线程类似于用户进程,通常用于并发处理些工作,它是一种在内核空间实现后台任务的方式,并且可以参与时间片轮转调度。

           线程一般在__init函数里面就定义、开启,而结束线程一般是在__exit函数中停止。

    //创建内核线程(方法一),返回值为创建线程的指针

    struct task_struct *kthread_create(int (*threadfn)(void *data),

                          void *data,const char namefmt[], ...);  

    参数1:线程函数指针,线程开启后将运行此函数

    参数2:函数参数data,传递给线程函数

    参数3:线程名称,这个函数可以像printk一样传入某种格式的线程名

    //内核线程创建后不会马上运行,需要通过以下函数启动

    int wake_up_process(struct task_struct *p);

    //***************************

    static int key_kthread_create(void)

    {

           int err = 0;

          

           key_thread = kthread_create(key_thread_func, &key, "key13_task");

        if(IS_ERR(key_thread)){

                 PRINTK("Unable to start kernel thread.\n");

                 err = PTR_ERR(key_thread);

                 key_thread = NULL;

                 return err;

        }

           wake_up_process(key_thread);

           return 0;

    }

    //*****************************

    static void key_kthread_stop(void)

    {

           if(key_thread){

                 kthread_stop(key_thread);

                 key_thread = NULL;

           }

           return;

    }

    //*****************************

    static int key_thread_func(void *data)

    {

           PRINTK("in function key_thread!\n");

           //daemonize("key_thread_func");

           //allow_signal(SIGKILL);

           for(;;){         //while循环实现线程任务

                 if(kthread_should_stop())  //检查是否stop

           //    if(signal_pending(current))

                        break;

                 if(key = translateKey(GPFDAT & 0x1c)){        

                        wake_up_interruptible(&my_queue);

                        condition = 1;

                 }    

                 schedule();          //进程间切换

           }

           return 0;

    }

    //***********************************************

    disable_irq关闭中断并等待中断处理完后返回, 而disable_irq_nosync立即返回

    下面从内核代码来找一下原因:

    先看一下disable_irq_nosync,内核代码中是这样解释的:

    /**
     *    disable_irq_nosync - disable an irq without waiting
     *    @irq: Interrupt to disable
     *
     *    Disable the selected interrupt line. Disables and Enables are
     *    nested.
     *    Unlike disable_irq(), this function does not ensure existing
     *    instances of the IRQ handler have completed before returning.
     *
     *    This function may be called from IRQ context.
     */
    void disable_irq_nosync(unsigned int irq)
    {
        struct irq_desc *desc = irq_to_desc(irq);
        unsigned long flags;

        if (!desc)
            return;

        chip_bus_lock(irq, desc);
        spin_lock_irqsave(&desc->lock, flags);
        __disable_irq(desc, irq, false);
        spin_unlock_irqrestore(&desc->lock, flags);
        chip_bus_sync_unlock(irq, desc);
    }


    关闭中断后程序返回, 如果在中断处理程序中, 那么会继续将中断处理程序执行完.


     

    /**
     * disable_irq - disable an irq and wait for completion
     * @irq: Interrupt to disable
     *
     * Disable the selected interrupt line. Enables and Disables are
     * nested.
     * This function waits for any pending IRQ handlers for this interrupt
     * to complete before returning. If you use this function while
     * holding a resource the IRQ handler may need you will deadlock.
     *
     * This function may be called - with care - from IRQ context.
     */
    void disable_irq(unsigned int irq)
    {
            struct irq_desc *desc = irq_desc + irq;
            if (irq >= NR_IRQS)
                    return;
            disable_irq_nosync(irq);
            if (desc->action)
                    synchronize_irq(irq);
    }

    关闭中断并等待中断处理完后返回.从代码中可以看到, disable_irq先是调用了disable_irq_nosync, 然后检测desc->action是否为1. 在中断处理程序中, action是置1的, 所以进入synchronize_irq函数中.


     

    /**
     * synchronize_irq - wait for pending IRQ handlers (on other CPUs)
     * @irq: interrupt number to wait for
     *
     * This function waits for any pending IRQ handlers for this interrupt
     * to complete before returning. If you use this function while
     * holding a resource the IRQ handler may need you will deadlock.
     *
     * This function may be called - with care - from IRQ context.
     */
    void synchronize_irq(unsigned int irq)
    {
     struct irq_desc *desc = irq_to_desc(irq);
     unsigned int status;
     if (!desc)
      return;
     do {
      unsigned long flags;
      /*
       * Wait until we're out of the critical section. This might
       * give the wrong answer due to the lack of memory barriers.
       */
      while (desc->status & IRQ_INPROGRESS)
       cpu_relax();
      /* Ok, that indicated we're done: double-check carefully. */
      spin_lock_irqsave(&desc->lock, flags);
      status = desc->status;
      spin_unlock_irqrestore(&desc->lock, flags);
      /* Oops, that failed? */
     } while (status & IRQ_INPROGRESS);
     /*
      * We made sure that no hardirq handler is running. Now verify
      * that no threaded handlers are active.
      */
     wait_event(desc->wait_for_threads, !atomic_read(&desc->threads_active));
    }


     

    注释中说明该函数是在等待中断处理程序的结束, 这也是disable_irq与disable_irq_nosync不同的主要所在. 但是在中断处理函数中调用会发生什么情况呢? 进入中断处理函数前IRQ_INPROGRESS会被__setup_irq设置, 所以程序会一直陷在while循环中, 而此时内核以经被独占, 这就导致系统死掉.

    总结:
    由于在disable_irq中会调用synchronize_irq函数等待中断返回, 所以在中断处理程序中不能使用disable_irq, 否则会导致cpu被synchronize_irq独占而发生系统崩溃.
     


     

    头尾介绍了,当然还的说说中断的心脏了,即中断服务程序,即申请中断时的第二个参数

    irqreturn_t handler(int irq,void *dev_id)

    {  

        ......     // 中断处理

        return IRQ_HANDLED;

    }

    该中断服务程序可以被多个中断调用,所以使用的时候,为防止产生冲突,需要加上自旋锁,此时即涉及到内核的并发控制

    抢占内核:必须配置 make menuconfig

    kernel feature

           freemption model

  • 相关阅读:
    C++设计模式 - 创建型模式之工厂模式
    【头歌-Python】8.5 中文词频统计(project) 1~5关
    京东数据平台:2023年9月京东智能家居行业数据分析
    提示词加神秘咒语让大模型更加聪明
    优雅地结合 Kotlin 特性深度解耦标题栏
    如何避免大语言模型绕过知识库乱答的情况?LlamaIndex 原理与应用简介
    浅谈拓展欧几里得算法
    KT148A语音芯片常见问题集锦|硬件|软件以及注意事项-长期更新
    LLC谐振变换器软启动过程分析与问题处理
    如何调整yolo混淆矩阵的大小,使其更加美观
  • 原文地址:https://blog.csdn.net/vx349014857/article/details/136167584