新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > arm驅動linux異步通知與異步IO

        arm驅動linux異步通知與異步IO

        作者: 時間:2016-11-19 來源:網絡 收藏
        《[arm驅動]linux異步通知異步IO》涉及內核驅動函數二個,內核結構體一個,分析了內核驅動函數二個;可參考的相關應用程序模板或內核驅動模板二個,可參考的相關應用程序模板或內核驅動三個

        描述:設備文件IO訪問:阻塞與非阻塞io訪問,poll函數提供較好的解決設備訪問的機制,但是如果有了異步通知整套機制就更加完整了

        本文引用地址:http://www.104case.com/article/201611/318221.htm

        一、阻塞 I/O,非阻塞IO,異步I/O

        1、阻塞 I/O :掛起進程一直等待設備可訪問后再訪問

        2、非阻塞IO:進程進行對設備訪問一次,不可訪問時,繼續執行下一條指令
        3、異步I/O:非常類似于硬件上“中斷”的概念(硬件去call軟件,內核去call應用程序);信號是在軟件層次上對中斷機制的一種模擬;

        a)原理:信號是異步的,一個進程不必通過任何操作來等待信號的到達;事實上:進程也不知道信號到底什么時候到達;“一個進程收到一個異步通知信號"與"處理器收到一個中斷請求"原理是一樣的;

        4、異步I/O通知隊列(async_queue):內核通過“內核異步通知的程序 fasync()函數”將設備文件fd描述符加入異步通知隊列(內核異步通知的鏈表)。當fd有I/O操作發生時內核通過kill_fasync()釋放(產生) SIGIO 信號,從而達到主動通知注冊過SIG_IO信號的應用程序。

        5、異步通知對象:首先它是設備文件,其次要注冊過fasync()函的文件;異步通知對象不是不是普通文件(不是隨便的/tmp/text.txt),因為普通文件沒有在內核中實現fasync()函數和kill_fasync()
        二、異步通訊應用程序部分
        模板一)設備文件的異步通知應用程序

        voidinput_handler(intnum){//信號處理函數
        }
        //打開目標設備
        fd = open("設備文件路徑如/dev/xxx", O_RDWR);
        //設置好目標設備的SIGIO信號處理程序;等待內核kill_fasync()釋放 SIGIO 信號
        signal(SIGIO,input_handler);
        //使當前進程變成文件的主人,這樣才能使文件中的信號發到當前進程
        fcntl(fd, F_SETOWN, getpid());
        //獲得當前fd的flag值
        oflags = fcntl(fd, F_GETFL);
        /*設置設備文件描述符號fd的FASYNC異步通知標志,
        即給fd添加異步通知模式,fasync()函數將fd加入異步IO通知隊列*/
        fcntl(fd, F_SETFL, oflags | FASYNC);

        圖示一、異步通知工作過程圖

        實例一)以標準輸入輸出設備異步通知

        #include
        #include
        #include
        #include
        #include
        #define MAX_LEN 100
        voidinput_handler(intnum)
        {
        chardata[MAX_LEN];
        intlen;
        len = read(STDIN_FILENO, &data, MAX_LEN);
        data[len] = 0;
        printf("input available :%sn", data);
        }
        voidsetFdAsync(intfd){
        intoflags;
        //當前進程變成文件的主人
        fcntl(fd, F_SETOWN, getpid());
        //本程序中fd = STDIN_FILENO標準輸入設備設備文件描述符號;普通文件內核中沒有實現FASYNC,不能使用異步通知
        oflags = fcntl(fd, F_GETFL);//
        //FASYNC在glibc 的fcntl.h文件中可以看到這樣的定義 #define FASYNC O_ASYNC
        fcntl(fd, F_SETFL, oflags | FASYNC);
        }
        voidmain(){
        intfd = STDIN_FILENO;//STDIN_FILENO輸入輸出設備描述符號,一般是鍵盤
        signal(SIGIO,input_handler);//設置好目標設備的SIGIO信號處理程序;等待內核kill_fasync()釋放 SIGIO 信號
        setFdAsync(fd);
        while(1);
        }

        運行結果:

        efgwrfgregr
        input available :efgwrfgregr
        sfsdf
        input available :sfsdf
        //本程序電腦上運行時,由于系統對STDIN_FILENO有特殊保護,while里面的程序運行了兩次,進程就被系統掛機休眠,此時cpu消耗為0;
        //但我在arm開發板上的linux2.6內核運行時,while正常,進程不被掛起,估計是沒鍵盤的原因...,也待解

        三、驅動程序部分
        驅動程序:一項數據結構和兩個函數
        結構體一)一項數據結構----- fasync_struct結構體
        內核源碼一)fasync_struct結構體內核源碼

        struct fasync_struct {
        int magic;//啟用設備文件鏡像,監聽文件是否變化(這個說法我猜的)
        int fa_fd;//文件描述符
        struct fasync_struct *fa_next; /* 異步通知單鏈表 */
        //filp是進程通過PCB中的文件描述符表找到該fd所指向的文件指針;在fopen流操作中使用file結構體指針它的優點是帶有I/O緩存
        struct file *fa_file;
        //struct file表示該進程打開的文件,其中有一個owner屬性,用來表示打開設備文件的進程
        };

        兩個函數
        內核部分函數一)fasync_helper處理設備文件異步通知的標志(O_ASYNC或FASYNC),將fd加入異步通知隊列函數

        fasync_helper(int fd, struct file * filp, int on, struct fasync_struct * * fapp);

        內核源碼二)fasync_helper內核源碼分析

        //第一次因為on = MODE = oflag | FASYNC,on!=0所以執行if (on)對struct fasync_struct **fapp進行初始化,
        //當程序釋放設備使用myfasync_drv_fasync(-1, file, 0),就執行goto out釋放中斷
        int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
        {
        struct fasync_struct *fa, **fp;
        struct fasync_struct *new = NULL;
        int result = 0;
        if (on) {//第一次分配fapp空間
        new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
        if (!new)
        return -ENOMEM;
        }
        write_lock_irq(&fasync_lock);
        for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {//第一次初始化fapp
        if (fa->fa_file == filp) {
        if(on) {
        fa->fa_fd = fd;
        kmem_cache_free(fasync_cache, new);
        } else {
        *fp = fa->fa_next;
        kmem_cache_free(fasync_cache, fa);
        result = 1;
        }
        goto out;
        }
        }
        if (on) {
        new->magic = FASYNC_MAGIC;
        new->fa_file = filp;
        new->fa_fd = fd;
        new->fa_next = *fapp;
        *fapp = new;
        result = 1;
        }
        out:
        write_unlock_irq(&fasync_lock);
        return result;
        }
        EXPORT_SYMBOL(fasync_helper);

        釋放信號函數
        內核部分函數二)kill_fasync(struct fasync_struct * * fp, int sig, int band)
        參數:sig就是我們要發送的信號;band(帶寬),一般都是使用POLL_IN,表示設備可讀,如果設備可寫,使用POLL_OUT
        內核源碼三)釋放(產生)異步讀信號函數

        void __kill_fasync(struct fasync_struct *fa, int sig, int band)
        {
        while (fa) {
        struct fown_struct * fown;
        //如果設備文件鏡像不存在如設備文件不存在(被刪除或改名)或取消了注冊FASYNC;鏡像映射失敗跳出kill_fasync,不產生信號
        if (fa->magic != FASYNC_MAGIC) {
        printk(KERN_ERR "kill_fasync: bad magic number in "
        "fasync_struct!n");
        return;
        }
        fown = &fa->fa_file->f_owner;
        /* Dont send SIGURG to processes which have not set a
        queued signum: SIGURG has its own default signalling
        mechanism. */
        if (!(sig == SIGURG && fown->signum == 0))
        send_sigio(fown, fa->fa_fd, band);
        fa = fa->fa_next;
        }
        }
        EXPORT_SYMBOL(__kill_fasync);

        模板二)信號的異步通知機制模板

        struct VirtualDisk{
        struct cdev cdev;
        //...其他全局變量....
        struct fasync_struct *async_queue;//異步結構體指針
        };
        /*異步讀信號*/
        static int myfasync_drv_fasync(int fd, struct file *file, int mode){
        struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/
        //....................
        return fasync_helper(fd, file, mode, &devp->async_queue);
        }
        static ssize_t myfasync_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos){
        struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/
        //...............
        //產生異步讀信號SIGIO
        if(devp->async_queue)kill_fasync(&devp->async_queue, SIGIO, POLL_IN);
        return 0;
        }
        static int myfasync_drv_release(struct inode *inode, struct file *file)
        {
        /*當設備關閉時,需要將fasync_struct從異步隊列中刪除/*
        myfasync_drv_fasync(-1, file, 0);
        return 0;
        }

        實例二)驅動程序完整實例:

        //“myfasync_drv”,"myfasync_","myfasync_drv"
        #include //模塊所需的大量符號和函數定義
        #include
        #include //文件系統相關的函數和頭文件
        #include //指定初始化和清除函數
        #include
        #include //cdev結構的頭文件包含
        #include
        #include
        //#include //包含驅動程序使用的大部分內核API的定義,包括睡眠函數以及各種變量聲明
        #include //在內核和用戶空間中移動數據的函數
        #include
        #include
        #include
        #include
        #define VIRTUALDISK_SIZE 0x1000//4k
        #define MEM_CLEAR 0x1
        #define VIRTUALDISK_MAJOR 250
        int VirtualDisk_major = VIRTUALDISK_MAJOR;
        struct fasync_struct *async_queue;//異步結構體指針
        struct VirtualDisk{
        struct cdev cdev;//詳細看cdev機制
        unsigned char mem[VIRTUALDISK_SIZE ];
        long count; /*記錄設備目前被多少設備打開*/

        };
        static struct class *myfasync_class;
        static struct class_device *myfasync_class_dev;
        struct VirtualDisk *VirtualDiskp;
        static int myfasync_drv_fasync(int fd, struct file *file, int mode){
        printk("myfasync_drv_fasync %dn", fd);
        return fasync_helper(fd, file, mode, &async_queue);
        }
        static int myfasync_drv_open(struct inode *inode, struct file *file)
        {
        printk("myfasync_drv openn");
        file->private_data = VirtualDiskp;
        VirtualDiskp->count++; /*增加設備打開次數*/
        return 0;
        }
        static int myfasync_drv_release(struct inode *inode, struct file *file)
        {
        printk("myfasync_drv releasen");
        VirtualDiskp->count--; /*減少設備打開次數*/
        myfasync_drv_fasync(-1, file, 0);//當設備關閉時,需要將fasync_struct從異步隊列中刪除
        return 0;
        }
        /*seek文件定位函數:seek()函數對文件定位的起始地址可以是文件開頭(SEEK_SET,0)、當前位置(SEEK_CUR,1)、文件尾(SEEK_END,2)*/
        static loff_t myfasync_drv_llseek(struct file *file, loff_t offset, int origin){
        loff_t ret = 0;/*返回的位置偏移*/

        switch (origin)
        {
        case SEEK_SET: /*相對文件開始位置偏移*/
        if (offset < 0)/*offset不合法*/
        {
        ret = - EINVAL; /*無效的指針*/
        break;
        }
        if ((unsigned int)offset > VIRTUALDISK_SIZE)/*偏移大于設備內存*/
        {
        ret = - EINVAL; /*無效的指針*/
        break;
        }
        file->f_pos = (unsigned int)offset; /*更新文件指針位置*/
        ret = file->f_pos;/*返回的位置偏移*/
        break;
        case SEEK_CUR: /*相對文件當前位置偏移*/
        if ((file->f_pos + offset) > VIRTUALDISK_SIZE)/*偏移大于設備內存*/
        {
        ret = - EINVAL;/*無效的指針*/
        break;
        }
        if ((file->f_pos + offset) < 0)/*指針不合法*/
        {
        ret = - EINVAL;/*無效的指針*/
        break;
        }
        file->f_pos += offset;/*更新文件指針位置*/
        ret = file->f_pos;/*返回的位置偏移*/
        break;
        default:
        ret = - EINVAL;/*無效的指針*/
        break;
        }
        return ret;
        }
        /*設備控制函數:ioctl()函數接受的MEM_CLEAR命令,這個命令將全局內存的有效數據長度清零,對于設備不支持的命令,ioctl()函數應該返回-EINVAL*/
        static int myfasync_drv_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){
        struct VirtualDisk *devp = file->private_data;/*獲得設備結構體指針*/

        switch (cmd)
        {
        case MEM_CLEAR:/*設備內存清零*/
        memset(devp->mem, 0, VIRTUALDISK_SIZE);
        printk(KERN_INFO "VirtualDisk is set to zeron");
        break;
        default:
        return - EINVAL;
        }
        return 0;
        }
        /*讀函數:讀寫函數主要是讓設備結構體的mem[]數組與用戶空間交互數據,并隨著訪問字節數變更返回用戶的文件讀寫偏移位置*/
        static ssize_t myfasync_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
        {
        unsigned long p = *ppos; /*記錄文件指針偏移位置*/
        unsigned int countt = count;/*記錄需要讀取的字節數*/
        int ret = 0; /*返回值*/
        struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/
        printk("myfasync_drv readn");
        /*分析和獲取有效的讀長度*/
        if (p >= VIRTUALDISK_SIZE ) /*要讀取的偏移大于設備的內存空間*/
        return 0;/*讀取地址錯誤*/
        if (countt > VIRTUALDISK_SIZE - p)/*要讀取的字節大于設備的內存空間*/
        countt = VIRTUALDISK_SIZE - p;/*將要讀取的字節數設為剩余的字節數*/
        /*內核空間->用戶空間交換數據*/
        if (copy_to_user(buf, (void*)(devp->mem + p), countt))
        {
        ret = - EFAULT;
        }
        else
        {
        *ppos += countt;
        ret = countt;
        printk("read %d bytes(s) is %ldn", countt, p);
        }
        printk("bytes(s) is %sn", devp->mem);
        return ret;
        }
        /*
        file 是文件指針,count 是請求的傳輸數據長度,buff 參數是指向用戶空間的緩沖區,這個緩沖區或者保存要寫入的數據,或者是一個存放新讀入數據的空緩沖區,該地址在內核空間不能直接讀寫,ppos 是一個指針指向一個"long offset type"對象, 它指出用戶正在存取的文件位置. 返回值是一個"signed size type。寫的位置相對于文件開頭的偏移。
        */
        static ssize_t myfasync_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
        {
        unsigned long p = *ppos; /*記錄文件指針偏移位置*/
        int ret = 0; /*返回值*/
        unsigned int countt = count;/*記錄需要寫入的字節數*/
        struct VirtualDisk *devp = file->private_data; /*獲得設備結構體指針*/
        printk("myfasync_drv writen");
        /*分析和獲取有效的寫長度*/
        if (p >= VIRTUALDISK_SIZE )/*要寫入的偏移大于設備的內存空間*/
        return 0;/*寫入地址錯誤*/
        if (countt > VIRTUALDISK_SIZE - p)/*要寫入的字節大于設備的內存空間*/
        countt = VIRTUALDISK_SIZE - p;/*將要寫入的字節數設為剩余的字節數*/
        /*用戶空間->內核空間*/
        if (copy_from_user(devp->mem + p, buf, countt))
        ret = - EFAULT;
        else
        {
        *ppos += countt;/*增加偏移位置*/
        ret = countt;/*返回實際的寫入字節數*/
        printk("written %u bytes(s) from%lu, buffer is %sn", countt, p, devp->mem);
        }
        if(async_queue){
        kill_fasync(&async_queue, SIGIO, POLL_IN);
        printk("write kill_fasyncn");
        }
        return ret;
        }
        static struct file_operations myfasync_drv_fops = {
        .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
        .open = myfasync_drv_open,
        .read = myfasync_drv_read,
        .write = myfasync_drv_write,
        .release = myfasync_drv_release,
        .llseek = myfasync_drv_llseek,
        .ioctl = myfasync_drv_ioctl,
        .fasync = myfasync_drv_fasync,
        };
        /*將 cdev 結構嵌入一個你自己的設備特定的結構,你應當初始化你已經分配的結構使用以上函數,有一個其他的 struct cdev 成員你需要初始化. 象 file_operations 結構,struct cdev 有一個擁有者成員,應當設置為 THIS_MODULE,一旦 cdev 結構建立, 最后的步驟是把它告訴內核, 調用:
        cdev_add(&dev->cdev, devno, 1);*/
        static void VirtualDisk_setup_cdev(struct VirtualDisk *dev, int minorIndex){
        int err;
        int devno = MKDEV(VirtualDisk_major, minorIndex);
        cdev_init(&dev->cdev, &myfasync_drv_fops);
        dev->cdev.owner = THIS_MODULE;
        err = cdev_add(&dev->cdev, devno, 1);
        if(err){
        printk("error %d cdev file addedn", err);
        }
        }
        static int myfasync_drv_init(void)
        {
        int result;
        dev_t devno = MKDEV(VirtualDisk_major, 0);
        if(VirtualDisk_major){
        result = register_chrdev_region(devno, 1, "myfasync_drv");
        }else{
        result = alloc_chrdev_region(&devno, 0, 1, "myfasync_drv");
        VirtualDisk_major = MAJOR(devno);
        }
        if(result < 0 ){
        return result;
        }
        VirtualDiskp = kmalloc(sizeof(struct VirtualDisk), GFP_KERNEL);
        if(!VirtualDiskp){
        result = -ENOMEM;
        goto fail_malloc;
        }
        memset(VirtualDiskp, 0, sizeof(struct VirtualDisk));
        VirtualDisk_setup_cdev(VirtualDiskp, 0);
        myfasync_class = class_create(THIS_MODULE, "myfasync_drv");
        if (IS_ERR(myfasync_class))
        return PTR_ERR(myfasync_class);
        myfasync_class_dev = class_device_create(myfasync_class, NULL, MKDEV(VirtualDisk_major, 0), NULL, "myfasync_drv"); /* /dev/xyz */
        if (IS_ERR(myfasync_class_dev))
        return PTR_ERR(myfasync_class_dev);
        return 0;
        fail_malloc:
        unregister_chrdev_region(devno, 1);
        return result;

        }
        static void myfasync_drv_exit(void)
        {
        cdev_del(&VirtualDiskp->cdev);
        kfree(VirtualDiskp);
        unregister_chrdev_region(MKDEV(VirtualDisk_major, 0), 1);
        class_device_unregister(myfasync_class_dev);
        class_destroy(myfasync_class);
        }
        module_init(myfasync_drv_init);
        module_exit(myfasync_drv_exit);
        MODULE_LICENSE("GPL");

        Makefile

        #myfasync_drv.c
        KERN_DIR = /workspacearm/linux-2.6.2.6
        all:
        make -C $(KERN_DIR) M=`pwd` modules
        cp myfasync_drv.ko /opt/fsmini/
        clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf timerlists.order
        obj-m += myfasync_drv.o

        實例三)驅動程序對應的測試的應用程序部分

        #include
        #include
        #include
        #include
        #include
        int myfd;
        int lenthe;
        void input_handler(int num)
        {
        char data[80];
        int len;
        lseek(myfd, -lenthe, SEEK_CUR);//移動偏移量到寫之前位置
        len = read(myfd, data, lenthe);
        //data[len] = ;
        printf("myfd = %d, len = %d buffuer input available :%sn",myfd, len, data);
        }
        void setFdAsync(int fd){
        int oflags;
        //當前進程變成文件的主人
        fcntl(fd, F_SETOWN, getpid());
        //本程序中fd = STDIN_FILENO標準輸入設備設備文件描述符號;普通文件內核中沒有實現FASYNC,不能使用異步通信
        oflags = fcntl(fd, F_GETFL);//
        //FASYNC在glibc 的fcntl.h文件中可以看到這樣的定義 #define FASYNC O_ASYNC
        fcntl(fd, F_SETFL, oflags | FASYNC);
        }
        int main(){
        myfd = open("/dev/myfasync_drv", O_RDWR);//STDIN_FILENO輸入輸出設備描述符號,一般是鍵盤
        printf("fd = %d,pid = %d", myfd, getpid());
        signal(SIGIO,input_handler);//設置好目標設備的SIGIO信號處理程序;等待內核kill_fasync()釋放 SIGIO 信號
        setFdAsync(myfd);
        printf("before whilen");
        while(1){
        char buffer[80];
        lenthe = read(STDIN_FILENO, buffer, 80);
        write(myfd, buffer, lenthe);
        }
        return 0;
        }

        我的Makefile

        objs := $(patsubst %c, %o, $(shell ls *.c))
        myarmgcc := /workspacearm/armlinuxgcc2626/bin/arm-linux-gcc
        mybutton.bin:$(objs)
        $(myarmgcc) -o $@ $^
        cp *.bin /opt/fsmini/
        %.o:%.c
        $(myarmgcc) -c -o $@ $<
        clean:
        rm -f *.bin *.o

        實驗結果

        # insmod myfasync_drv.ko
        # ./mybutton.bin
        myfasync_drv open//對應應用程序myfd = open("/dev/myfasync_drv",調用了內核驅動open函數
        myfasync_drv_fasync 3//對應應用程序fcntl(fd, F_SETFL, oflags | FASYNC);調用了內核驅動的myfasync_drv_fasync()函數
        //
        fd = 3,pid = 793before while//while前的進程信息輸出
        hello//鍵盤輸入hello
        myfasync_drv write//調用驅動程序write函數
        written 6 bytes(s) from0, buffer is hello//驅動程序write函數內部輸出
        write kill_fasync//內涵write函數中,執行kill_fasync(&async_queue, SIGIO, POLL_IN);釋放SIGIO信號
        myfasync_drv read//此時應用程序收到中斷,應用程序執行read函數,read對應內核驅動的read
        read 6 bytes(s) is 0//內核驅動read打印輸出
        bytes(s) is hello //內核驅動read打印輸出

        myfd = 3, len = 6 buffuer input available :hello//應用程序input_handler函數輸出驅動的寫入值
        //下面是while第二次執行
        it is ok
        myfasync_drv write
        written 9 bytes(s) from6, buffer is hello
        it is ok

        write kill_fasync
        myfasync_drv read
        read 9 bytes(s) is 6
        bytes(s) is hello
        it is ok

        myfd = 3, len = 9 buffuer input available :it is ok
        //按ctrl+c退出程序,會執行myfasync_drv_release中myfasync_drv_fasync(-1, file, 0),釋放本進程的異步通知
        myfasync_drv release
        myfasync_drv_fasync -1

        #

        四、異步IO缺陷:當有多個文件發送異步通知信號給一個進程時,進程無法知道是哪個文件發送的信號,這時候“設備文件 ”還是要借助poll機制完成IO;(應用程序中使用select)



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 墨竹工卡县| 成安县| 渭南市| 清涧县| 田林县| 侯马市| 金川县| 牙克石市| 三台县| 固原市| 武邑县| 高青县| 泰宁县| 黄石市| 措勤县| 潮安县| 宜黄县| 勃利县| 桦南县| 九台市| 满洲里市| 海兴县| 台中县| 如皋市| 沧州市| 隆回县| 武平县| 梁河县| 巴彦县| 自治县| 桐乡市| 涿州市| 武汉市| 镇雄县| 邯郸市| 兴化市| 龙井市| 义马市| 英德市| 个旧市| 江孜县|