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 第 193 场周赛
    LeetCode 每日一题 15. 三数之和
    LeetCode 每日一题 739. 每日温度
    [转]邹承鲁院士写他是如何读文献
    LeetCode 每日一题 面试题46. 把数字翻译成字符串
    LeetCode 每日一题 990. 等式方程的可满足性
    传说中编程界的龙书、虎书、鲸书、魔法书…… 指的都是哪些?
    LeetCode 每日一题 238. 除自身以外数组的乘积
    C++ 关键字 explicit
    LeetCode 每日一题 837. 新21点
  • 原文地址:https://www.cnblogs.com/zhuangquan/p/11641804.html
Copyright © 2011-2022 走看看