zoukankan      html  css  js  c++  java
  • 《驱动学习

    1.异步通知

      前面介绍的几种按键相关:

    •   应用程序中使用while(1),一直去轮询查按键的状态。(这样会导致应用程序一直占用cpu资源)
    •   使用中断的方式,在应用程序中通过read,然后进入驱动程序中,使应用程序进入休眠。然后发生中断的时候,在中断服务函数中将进程唤醒,返回按键值。(导致应用程序一直在休眠)
    •   使用poll机制,如果在规定的时间内没用中断发生,则超时返回。

      以上三种都是针对应用程序。

      接下来介绍一种异步通知机制:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,是一种“信号驱动的异步I/O”。

      信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候会到达。

      kill -9  pid是我们经常使用用来结束某个进程得命令。其实这个就是一种异步通知。

      kill是一个进程,用作发送信号

      9是一个信号

      pid是另一个进程,是接收信号

      当pid这个进程接收到9这个信号,就会调用对应得函数。默认状态就是结束进程。

      举例:

    #include <signal.h>
    
    
    void my_signal_test(int signum)
    {
        static int cnt = 0;
        printf("signal = %d,%d times
    ",signum,++cnt);
    }
    
    int main(int argc,char **argv)
    {
        signal(SIGUSR1, my_signal_test);//建立一个信号函数,接收的信号是SIGUSR1表示用户可用的信号值
    
        while(1)
        {
            sleep(1000);
        }
        return 0;
    }

       signal(SIGUSR1, my_signal_test);就是如果接收到SIGUSR1信号,就会调用my_signal_test函数。然后在函数中就打印信息。

      这样就可以在命令行中先运行./signal  &,通过ps查看signal进程得pid,然后kill   USR1   pid。就可以查看到打印信息。

    2.异步通知驱动编写

    static struct file_operations sencod_drv_fops = {
        .owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
        .open    =  fifth_drv_open,     
        .read     =    fifth_drv_read,       
        .release =  fifth_drv_close,
        .poll    =  fifth_drv_poll,
        .fasync     =  fifth_drv_fasync,
    };

      首先在file_operations结构体中定义好,

    fasync函数:

    static struct fasync_struct *button_async;

    static
    int fifth_drv_fasync (int fd, struct file *filp, int on) { printk("driver: fifth_drv_fasync "); return fasync_helper (fd, filp, on, &button_async); }

       定义一个fasync_struct结构体指针,然后通过fasync_helper对这个指针变量进行初始化。

    fasync_helper函数:

    int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
    {
        struct fasync_struct *new = NULL;
    
          if (on) {
            new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
            if (!new)
                return -ENOMEM;
        }
    
        if (on) {
            new->magic = FASYNC_MAGIC;
            new->fa_file = filp;
            new->fa_fd = fd;
            new->fa_next = *fapp;
            *fapp = new;
            result = 1;
        }  
    }

      可以从源码上看出,就是将button_async申请一块内存,然后进行一系列初始化。

    中断服务函数:

    static irqreturn_t buttons_irq(int irq, void *dev_id)
    {
            /**/
        kill_fasync (&button_async, SIGIO, POLL_IN);
        
    }

      通过kill_fasync来发送SIGIO信号到应用程序中。

    3.分析fcntl源码

      在应用程序中调用fcntl函数,就会调用SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)。(在旧内核是sys_fcntl)

    SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
    {    
        struct file *filp;
        long err = -EBADF;
    
        filp = fget(fd);   //通过文件描述符获得对应关联的文件指针
        if (!filp)
            goto out;
      /*略*/
        err = do_fcntl(fd, cmd, arg, filp);   //调用do_fcntl函数
    
         fput(filp);
    out:
        return err;
    }

    do_fcntl函数:

    static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
            struct file *filp)
    {
        long err = -EINVAL;
    
        switch (cmd) {
        case F_DUPFD:
        case F_DUPFD_CLOEXEC:
            if (arg >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
                break;
            err = alloc_fd(arg, cmd == F_DUPFD_CLOEXEC ? O_CLOEXEC : 0);
            if (err >= 0) {
                get_file(filp);
                fd_install(err, filp);
            }
            break;
        case F_GETFD:
            err = get_close_on_exec(fd) ? FD_CLOEXEC : 0;
            break;
        case F_SETFD:
            err = 0;
            set_close_on_exec(fd, arg & FD_CLOEXEC);
            break;
        case F_GETFL:
            err = filp->f_flags;
            break;
        case F_SETFL:
            err = setfl(fd, filp, arg);
            break;
        case F_GETLK:
            err = fcntl_getlk(filp, (struct flock __user *) arg);
            break;
        case F_SETLK:
        case F_SETLKW:
            err = fcntl_setlk(fd, filp, cmd, (struct flock __user *) arg);
            break;
        case F_GETOWN:
            /*
             * XXX If f_owner is a process group, the
             * negative return value will get converted
             * into an error.  Oops.  If we keep the
             * current syscall conventions, the only way
             * to fix this will be in libc.
             */
            err = f_getown(filp);
            force_successful_syscall_return();
            break;
        case F_SETOWN:
            err = f_setown(filp, arg, 1);
            break;
        case F_GETOWN_EX:
            err = f_getown_ex(filp, arg);
            break;
        case F_SETOWN_EX:
            err = f_setown_ex(filp, arg);
            break;
        case F_GETSIG:
            err = filp->f_owner.signum;
            break;
        case F_SETSIG:
            /* arg == 0 restores default behaviour. */
            if (!valid_signal(arg)) {
                break;
            }
            err = 0;
            filp->f_owner.signum = arg;
            break;
        case F_GETLEASE:
            err = fcntl_getlease(filp);
            break;
        case F_SETLEASE:
            err = fcntl_setlease(fd, filp, arg);
            break;
        case F_NOTIFY:
            err = fcntl_dirnotify(fd, filp, arg);
            break;
        default:
            break;
        }
        return err;
    }

      do_fcntl函数里面就是一个switch,根据传进来的cmd执行相对应的函数。

      其中主要注意两个F_SETOWN和F_SETFL

    F_SETOWN:

    最后主要调用:
    static
    void f_modown(struct file *filp, struct pid *pid, enum pid_type type, int force) { write_lock_irq(&filp->f_owner.lock); if (force || !filp->f_owner.pid) { put_pid(filp->f_owner.pid); filp->f_owner.pid = get_pid(pid); filp->f_owner.pid_type = type; if (pid) { const struct cred *cred = current_cred(); filp->f_owner.uid = cred->uid; filp->f_owner.euid = cred->euid; } } write_unlock_irq(&filp->f_owner.lock); }

    F_SETOWN被调用时对filp->f_owner赋值,此外什么也不做;

    F_SETFL:

    static int setfl(int fd, struct file * filp, unsigned long arg)
    {  
        /*略*/
    if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op &&filp->f_op->fasync)
         {    error
    = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);    if (error < 0)   goto out;   if (error > 0)    error = 0; }
    /*略*/ }

      在执行F_SETFL启用FASYNC时,调用驱动程序的fasync方法。只要filp->f_flags中的FASYNC标识发生了变化,就会调用该方法,以便把这个变化通知驱动程序,使其能正确响应。文件打开时,FASYNC标志被默认为是清除的。

      Linux的这种通用方法基于一个数据结构和两个函数:

    struct fasync_struct {
        int magic;
        int fa_fd;//文件描述符
        struct  fasync_struct   *fa_next; /* singly linked list *///异步通知队列
        struct  file        *fa_file;//文件指针
    };
    
    /* SMP safe fasync helpers: */
    extern int fasync_helper(int, struct file *, int, struct fasync_struct **);
    /* can be called from interrupts */
    extern void kill_fasync(struct fasync_struct **, int, int);

      当一个打开的文件的FASYNC标志被修改时,调用fasync_helper函数以便从相关的进程表中添加或删除文件。
      当数据到达时,可使用kill_fasync函数通知所有的相关进程。(在UNIX语义中,kill这个词常用来向进程发送信号,而不是杀死某个进程,raise则用于向自身发送信号)。

    4.应用程序的编写

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <poll.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    /* fifthdrvtest 
      */
    int fd;
    
    void my_signal_fun(int signum)
    {
        unsigned char key_val;
        read(fd, &key_val, 1);
        printf("key_val: 0x%x
    ", key_val);
    }
    
    int main(int argc, char **argv)
    {
        unsigned char key_val;
        int ret;
        int Oflags;
    
        signal(SIGIO, my_signal_fun);
        
        fd = open("/dev/buttons", O_RDWR);
        if (fd < 0)
        {
            printf("can't open!
    ");
        }
    
        fcntl(fd, F_SETOWN, getpid());
        
        Oflags = fcntl(fd, F_GETFL); 
        
        fcntl(fd, F_SETFL, Oflags | FASYNC);
    
    
        while (1)
        {
            sleep(1000);
        }
        
        return 0;
    }

      signal(SIGIO, my_signal_fun);

      让my_signal_fun去处理SIGIO信号

      fcntl(fd, F_SETOWN, getpid())

      为了启动文件的异步通知机制,用户程序必须执行两个步骤。首先,她们指定一个进程作为文件的“属主(owner)”。当进程使用fcntl系统调用执行F_SETOWN命令时,属主进程的进程ID号就被保存在filp->f_owner中。这一步是必需的,目的是为了让内核知道应该通知哪个进程。

       Oflags = fcntl(fd, F_GETFL); 
       fcntl(fd, F_SETFL, Oflags | FASYNC);

      然后为了真正启动异步通知机制,用户程序还必须在设备中设置FASYNC标志,这通过fcntl的F_SETFL命令完成的。
      执行完这两个步骤之后,输入文件就可以在新数据到达时请求发送一个SIGIO信号。该信号被发送到存放在filp->f_owner中的进程(如果是负值就是进程组)。

       https://blog.csdn.net/wenqian1991/article/details/50333655

  • 相关阅读:
    LeetCode 83. Remove Duplicates from Sorted List (从有序链表中去除重复项)
    LeetCode 21. Merge Two Sorted Lists (合并两个有序链表)
    LeetCode 720. Longest Word in Dictionary (字典里最长的单词)
    LeetCode 690. Employee Importance (职员的重要值)
    LeetCode 645. Set Mismatch (集合不匹配)
    LeetCode 500. Keyboard Row (键盘行)
    LeetCode 463. Island Perimeter (岛的周长)
    115.Distinct Subsequences
    55.Jump Game
    124.Binary Tree Maximum Path Sum
  • 原文地址:https://www.cnblogs.com/zhuangquan/p/11641804.html
Copyright © 2011-2022 走看看