《驱动学习 - 异步通知》

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\n",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\n"); 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\n", 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!\n");
    }

    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

 

posted @ 2019-10-09 14:53  一个不知道干嘛的小萌新  阅读(268)  评论(0编辑  收藏  举报