專注電子技術(shù)學(xué)習(xí)與研究
當(dāng)前位置:單片機(jī)教程網(wǎng) >> MCU設(shè)計實(shí)例 >> 瀏覽文章

linux驅(qū)動之內(nèi)核定時器驅(qū)動設(shè)計

作者:龔平   來源:本站原創(chuàng)   點(diǎn)擊數(shù):  更新時間:2014年03月14日   【字體:

我的環(huán)境:
Fedora 14 內(nèi)核版本為2.6.38.1
開發(fā)板:ARM9  TQ2440
移植內(nèi)核版本:linux-2.6.30.4

定時器在linux內(nèi)核中主要是采用一個結(jié)構(gòu)體實(shí)現(xiàn)的。但是需要注意定時器是一個只運(yùn)行一次的對象,也就是當(dāng)一個定時器結(jié)束以后,還需要重現(xiàn)添加定時器。但是可以采用mod_timer()函數(shù)動態(tài)的改變定時器到達(dá)時間。
這個驅(qū)動主要實(shí)現(xiàn)內(nèi)核定時器的基本操作。內(nèi)核定時器主要是是通過下面的結(jié)構(gòu)體struct timer_list實(shí)現(xiàn)。需要的頭文件包括#include<linux/timer.h>,但是在實(shí)際開發(fā)過程中不需要包含該頭文件,因?yàn)樵趕ched.h中包含了該頭文件。

    struct timer_list {
        struct list_head entry;
        unsigned long expires;

        void (*function)(unsigned long);
        unsigned long data;

        struct tvec_base *base;
    #ifdef CONFIG_TIMER_STATS
        void *start_site;
        char start_comm[16];
        int start_pid;
    #endif
    #ifdef CONFIG_LOCKDEP
        struct lockdep_map lockdep_map;
    #endif
    };

定時器的實(shí)現(xiàn)主要是該結(jié)構(gòu)體的填充和部分函數(shù)的配合即可完成。其中紅色的部分是最主要的幾個元素,1、expires主要是用來定義定時器到期的時間,通常采用jiffies這個全局變量和HZ這個全局變量配合設(shè)置該元素的值。比如expires = jiffies + n*HZ,其中jiffies是自啟動以來的滴答數(shù),HZ是一秒種的滴答數(shù)。
2、function可以知道是一個函數(shù)指針,該函數(shù)就是定時器的處理函數(shù),類似我們在中斷中的中斷函數(shù),其實(shí)定時器和中斷有很大的相似性。定時器處理函數(shù)是自己定義的函數(shù)。
3、data通常是實(shí)現(xiàn)參數(shù)的傳遞,從function的參數(shù)類型可以知道,data可以作為定時器處理函數(shù)的參數(shù)。
其他的元素可以通過內(nèi)核的函數(shù)來初始化。

初始化函數(shù)為:
init_timer(struct timer_list * timer);
或者直接DEFINE_TIMER宏實(shí)現(xiàn)定義和初始化操作。

    #define DEFINE_TIMER(_name, _function, _expires, _data)        \
        struct timer_list _name =                \
            TIMER_INITIALIZER(_function, _expires, _data)

添加定時器到內(nèi)核的函數(shù):

    void add_timer(struct timer_list *timer)
    {
        BUG_ON(timer_pending(timer));
        mod_timer(timer, timer->expires);
    }

刪除定時器函數(shù),如果定時器的定時時間還沒有到達(dá),那么才可以刪除定時器:
int del_timer(struct timer_list *timer)

修改定時器的到達(dá)時間,該函數(shù)的特點(diǎn)是,不管定時器是否到達(dá)時間,都會重現(xiàn)添加一個定時器到內(nèi)核。所以可以在定時處理函數(shù)中可以調(diào)用該函數(shù)修改需要重新定義的到達(dá)時間。
int mode_timer(struct timer_list *timer,unsigned long expires)

    int mod_timer(struct timer_list *timer, unsigned long expires)
    {
        /*
         * This is a common optimization triggered by the
         * networking code - if the timer is re-modified
         * to be the same thing then just return:
         */
        if (timer->expires == expires && timer_pending(timer))
            return 1;

        /*注意調(diào)用的條件,也就是說明當(dāng)前的定時器為鏈表的最后一個*/
        return __mod_timer(timer, expires, false);
    }

    static inline int
    __mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)
    {
        struct tvec_base *base, *new_base;
        unsigned long flags;
        int ret;

        ret = 0;

        timer_stats_timer_set_start_info(timer);
        BUG_ON(!timer->function);

        base = lock_timer_base(timer, &flags);

        if (timer_pending(timer)) {
            detach_timer(timer, 0);
            ret = 1;
        } else {
            if (pending_only)
                goto out_unlock;
        }

        debug_timer_activate(timer);

        new_base = __get_cpu_var(tvec_bases);

        if (base != new_base) {
            /*
             * We are trying to schedule the timer on the local CPU.
             * However we can't change timer's base while it is running,
             * otherwise del_timer_sync() can't detect that the timer's
             * handler yet has not finished. This also guarantees that
             * the timer is serialized wrt itself.
             */
            if (likely(base->running_timer != timer)) {
                /* See the comment in lock_timer_base() */
                timer_set_base(timer, NULL);
                spin_unlock(&base->lock);
                base = new_base;
                spin_lock(&base->lock);
                timer_set_base(timer, base);
            }
        }

        timer->expires = expires;
        internal_add_timer(base, timer);

    out_unlock:
        spin_unlock_irqrestore(&base->lock, flags);

        return ret;
    }

    static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
    {
        unsigned long expires = timer->expires;
        unsigned long idx = expires - base->timer_jiffies;
        struct list_head *vec;

        if (idx < TVR_SIZE) {
            int i = expires & TVR_MASK;
            vec = base->tv1.vec + i;
        } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
            int i = (expires >> TVR_BITS) & TVN_MASK;
            vec = base->tv2.vec + i;
        } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
            int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
            vec = base->tv3.vec + i;
        } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
            int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
            vec = base->tv4.vec + i;
        } else if ((signed long) idx < 0) {
            /*
             * Can happen if you add a timer with expires == jiffies,
             * or you set a timer to go off in the past
             */
            vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
        } else {
            int i;
            /* If the timeout is larger than 0xffffffff on 64-bit
             * architectures then we use the maximum timeout:
             */
            if (idx > 0xffffffffUL) {
                idx = 0xffffffffUL;
                expires = idx + base->timer_jiffies;
            }
            i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
            vec = base->tv5.vec + i;
        }
        /*
         * Timers are FIFO:
         */
        /*添加到鏈表的最后,這說明mod_timer實(shí)現(xiàn)了重新注冊一個定時器的操作*/
        list_add_tail(&timer->entry, vec);
    }

從上面的分析可以看出,mod_timer的實(shí)現(xiàn)過程比較復(fù)雜,但是基本上說明了mod_timer函數(shù)重新注冊定時器的操作過程。
一般而言定時器的基本操作主要是上面的幾個函數(shù)。
我的基于內(nèi)核定時器的驅(qū)動函數(shù)如下,參考了宋寶華的Linux設(shè)備驅(qū)動開發(fā)詳解(第二版)。
驅(qū)動程序:

    #include<linux/module.h>
    #include<linux/types.h>
    #include<linux/fs.h>
    #include<linux/errno.h>
    #include<linux/mm.h>
    #include<linux/sched.h>
    #include<linux/init.h>
    #include<linux/cdev.h>
    #include<asm/io.h>
    #include<asm/uaccess.h>
    #include<linux/device.h>

    /*采用宏定義設(shè)置設(shè)備的主設(shè)備號*/
    #define SECOND_MAJOR    0
    /*靜態(tài)的分別保存靜態(tài)主設(shè)備號的變量*/
    static int second_major = SECOND_MAJOR;

    /*設(shè)備結(jié)構(gòu)體,通常在設(shè)備中包含需要的設(shè)備,比如字符、塊等類型*/
    struct second_dev{
        /*添加設(shè)備類型,
        我認(rèn)為可以采用一個聯(lián)合體,
        包含塊設(shè)備或者字符設(shè)備,類似inode的實(shí)現(xiàn)方法,
        這樣可以提高結(jié)構(gòu)體的通用性
        */
        struct cdev cdev;
        /*原子變量,用來統(tǒng)計*/
        atomic_t counter;
        /*添加內(nèi)核定時器結(jié)構(gòu)體變量*/
        struct timer_list s_timer;
       
        /*用于動態(tài)創(chuàng)建設(shè)備文件的設(shè)備類*/
        struct class *myclass;
    };

    /*結(jié)構(gòu)體指針或者采用全局變量直接定義結(jié)構(gòu)都可以*/
    struct second_dev *second_devp;

    /*如果定時時間到了,定時器的處理函數(shù)*/
    static void second_timer_handler(unsigned long arg)
    {
        /*
        修改定時器中的到期時間,增加時間為1s,
        需要注意的是mod_timer函數(shù)是重新注冊定時器到內(nèi)核
        而不管定時器是否被運(yùn)行過
        */
        mod_timer(&second_devp->s_timer,jiffies + HZ);
        /*原子變量的增加*/
        atomic_inc(&second_devp->counter);
        /*輸出jiffies值*/
        printk(KERN_NOTICE "Current jiffies is %d\n",jiffies);
    }

    /*open函數(shù)實(shí)現(xiàn)*/
    static int second_open(struct inode *inode,struct file *filp)
    {
        /*初始化定義的內(nèi)核定時器*/
        init_timer(&second_devp->s_timer);
        /*指定內(nèi)核定時器的處理函數(shù)是上面定義好的函數(shù)*/
        second_devp->s_timer.function = second_timer_handler;
        /*指定定時間隔是1s*/
        second_devp->s_timer.expires = jiffies + HZ;

        /*將定時器添加到內(nèi)核*/
        add_timer(&second_devp->s_timer);
        /*同時設(shè)備相關(guān)的統(tǒng)計值為0*/
        atomic_set(&second_devp->counter,0);

        return 0;
    }

    /*release函數(shù)的實(shí)現(xiàn)*/
    static int second_release(struct inode *inode,struct file *filp)
    {
        /*如果沒有到時間就關(guān)閉設(shè)備,直接刪除定時器*/
        del_timer(&second_devp->s_timer);

        return 0;
    }

    /*read函數(shù)的實(shí)現(xiàn)*/
    static ssize_t second_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
    {
        int counter;
       
        /*讀當(dāng)前的值*/
        counter = atomic_read(&second_devp->counter);
       
         /*
         采用put_user實(shí)現(xiàn)數(shù)值的傳送
         put_user函數(shù)存在對指針變量的檢查,
         因此不需要檢測指針是否正確
         */
        if(put_user(counter,(int *)buf))
            return -EFAULT;
        else
            /*返回數(shù)據(jù)大小*/
            return sizeof(unsigned int);
    }

    /*具體的文件操作集合*/
    static const struct file_operations second_fops =
    {
        /*這是擁有者*/
        .owner = THIS_MODULE,
        .open = second_open,
        .release = second_release,
        .read = second_read,
    };

    /*初始化函數(shù)*/
    static int __init second_init(void)
    {
        int ret;
        /*設(shè)備號的申請,創(chuàng)建*/
        dev_t devno = MKDEV(second_major,0);

        /*靜態(tài)申請設(shè)備號*/
        if(second_major)
        {
            ret = register_chrdev_region(devno,1,"second");
        }
        /*動態(tài)申請設(shè)備號*/
        else
        {
            ret = alloc_chrdev_region(&devno,0,1,"second");
            second_major = MAJOR(devno);
        }
        if(ret < 0)
        {
            return ret;
        }
       
        /*分配設(shè)備結(jié)構(gòu)體的地址空間*/
        second_devp = kmalloc(sizeof(struct second_dev),GFP_KERNEL);
        /*檢查是否分配正確*/
        if(!second_devp)
        {
            ret = -ENOMEM;
            goto fail_malloc;
        }
        /*清零分配的空間*/
        memset(second_devp,0,sizeof(struct second_dev));
        /*創(chuàng)建設(shè)備類,用于自動創(chuàng)建設(shè)備文件*/
        second_devp->myclass = class_create(THIS_MODULE,"second_timer_class");
       
        /*字符設(shè)備初始化,綁定相關(guān)操作到設(shè)備*/
        cdev_init(&second_devp->cdev,&second_fops);
        /*設(shè)備的擁有者*/
        second_devp->cdev.owner = THIS_MODULE,
        /*添加設(shè)備到內(nèi)核*/
        ret = cdev_add(&second_devp->cdev,devno,1);
       
        /*錯誤處理*/
        if(ret)
        {
            printk(KERN_NOTICE "ERROR %d\n ",ret);
            goto fail_malloc;
        }
        /*依據(jù)以前創(chuàng)建的設(shè)備類,創(chuàng)建設(shè)備*/
        device_create(second_devp->myclass,NULL,devno,NULL,"second%d",0);
        return 0;

    /*錯誤操作*/
    fail_malloc:
        unregister_chrdev_region(devno,1);
        return ret;
    }

    /*退出函數(shù)*/
    static void __exit second_exit(void)
    {
        /*釋放設(shè)備*/
        device_destroy(second_devp->myclass,MKDEV(second_major,0));
        /*刪除字符設(shè)備*/
        cdev_del(&second_devp->cdev);
        /*釋放設(shè)備類*/
        class_destroy(second_devp->myclass);
        /*釋放分配的內(nèi)存空間大小*/   
        kfree(second_devp);
        /*釋放設(shè)備號*/
        unregister_chrdev_region(MKDEV(second_major,0),1);
    }

    /*卸載和加載*/
    module_init(second_init);
    module_exit(second_exit);

    /*LICENSE和作者信息*/
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("GP-<gp19861112@yahoo.com.cn>");

應(yīng)用程序:

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<linux/fs.h>
    #include<unistd.h>
    #include<fcntl.h>

    int main()
    {
        int fd;
        int counter = 0;
        int old_counter = 0;
       
        fd = open("/dev/second0",O_RDONLY);

        if(fd != -1)
        {
            while(1)
            {
                read(fd,&counter,sizeof(unsigned int));
                if(counter != old_counter)
                {
                    printf("second after open /dev/second0 : %d\n",counter);
                    old_counter = counter;
                }
            }
        }
        else
        {
            printf("Device open failure\n");
            exit(1);
        }
        exit(0);
    }

實(shí)驗(yàn)效果:
[root@EmbedSky Test]# ./app-timer                                              
Current jiffies is 2137721                                                     
second after open /dev/second0 : 1                                             
Current jiffies is 2137921                                                     
second after open /dev/second0 : 2                                             
Current jiffies is 2138121                                                     
second after open /dev/second0 : 3                                             
Current jiffies is 2138321                                                     
second after open /dev/second0 : 4                                             
Current jiffies is 2138521                                                     
second after open /dev/second0 : 5                                             
Current jiffies is 2138721                                                     
second after open /dev/second0 : 6   

以上的結(jié)果表明內(nèi)核定時器基本實(shí)現(xiàn)了效果,但從實(shí)驗(yàn)結(jié)果看好像為每兩秒實(shí)現(xiàn)一次顯示。具體的原因還有待于再次分析,因?yàn)閍rm中的HZ應(yīng)該為100,而不是200。

關(guān)閉窗口

相關(guān)文章