Linux驱动开发学习笔记(二)异步通知

应用层程序

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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");
	}

    /* Set the process ID or process group ID that will receive SIGIO and 
     * SIGURG signals for events on file descriptor fd to the ID given in arg.  
     * A process ID is specified as a positive value; a process group ID is  
     * specified  as  a  negative value.  Most commonly, the calling process 
     * specifies itself as the owner (that is, arg is specified as getpid(2)).
     * 设置这个进程会接受文将描述符fd发出到SIGIO和SIGURG信号
     */
	fcntl(fd, F_SETOWN, getpid());
	
	Oflags = fcntl(fd, F_GETFL); 
	
    /* 最终会调用驱动程序的 .fasync 函数(后面分析) */
	fcntl(fd, F_SETFL, Oflags | FASYNC);

	while (1) {
		sleep(1000);
	}
	
	return 0;
}

下面主要先对 fcntl(fd, F_SETOWN, getpid()); 这个调用进行分析

应用层调用 fcntl 来进行文件描述符相关操作,会调用内核对应的 sys_fcntl 函数

asmlinkage long sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
{	
	struct file *filp;
	long err = -EBADF;

	filp = fget(fd);
	if (!filp)
		goto out;

    /* 这个security_file_fcntl在不开启selinux时,默认操作是直接 return 0 
     * 主要关于安全方面的东西,可以忽视它
     */
	err = security_file_fcntl(filp, cmd, arg);
	if (err) {
		fput(filp);
		return err;
	}
	
    /* 主要工作在这个函数里面执行 */
	err = do_fcntl(fd, cmd, arg, filp);

 	fput(filp);
out:
	return err;
}


static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
		struct file *filp)
{
	long err = -EINVAL;

    /* 根据 cmd 的类型,调用对应的 fcntl_cmd函数进行处理
     */
	switch (cmd) {
            
		...
            
	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_GETSIG:
		err = filp->f_owner.signum;
		break;
		
        ...
            
	default:
		break;
	}
	return err;
}

我们主要关注的是 F_SETOWN命令

case F_SETOWN:
	/* 我们关注这个函数 */
    err = f_setown(filp, arg, 1);
    break;

/* arg为我们在应用层设置的 flags
 * filp文件描述对应的文件对象指针
 * force为1
 */
int f_setown(struct file *filp, unsigned long arg, int force)
{
	enum pid_type type;
	struct pid *pid;
	int who = arg;
	int result;
	type = PIDTYPE_PID;
	if (who < 0) {
		type = PIDTYPE_PGID;
		who = -who;
	}
	rcu_read_lock();
	pid = find_pid(who);
    /* 主要是这个函数 */
	result = __f_setown(filp, pid, type, force);
	rcu_read_unlock();
	return result;
}

int __f_setown(struct file *filp, struct pid *pid, enum pid_type type,
		int force)
{
	int err;
	
	err = security_file_set_fowner(filp);
	if (err)
		return err;
	
    /* 继续往下分析 */
	f_modown(filp, pid, type, current->uid, current->euid, force);
	return 0;
}

/* pid 当前进程的pid
 * type 为PIDTYPE_PGID
 * uid 当前进程的uid
 * euid 当前进程的euid
 */
static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,
                     uid_t uid, uid_t euid, 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;
		filp->f_owner.uid = uid;
		filp->f_owner.euid = euid;
	}
	write_unlock_irq(&filp->f_owner.lock);
}

一路分析下来,我们可以知道最终 fcntl(fd, F_SETOWN, getpid())会将当前进程的各种信息放入文件描述符的文件对象 file 中,这个为了后面驱动程序发送信号时,可以通过 filp->f_owner 中的信息找到应用程序。

下面我们再对 fcntl(fd, F_SETFL, Oflags | FASYNC)进行分析,加入了一个 FASYNC 标志,那么 fcntl 最终会调用驱动程序的file_operations 中实现的 .fasync 函数。

在此之前,fcntl 根据 cmd 中指定的为 F_SETFLsys_fcntl() 调用 setfl() 进入 setfl(fd, filp, arg) 下面对 setfl() 进行详细的分析。

static int setfl(int fd, struct file * filp, unsigned long arg)
{
	struct inode * inode = filp->f_path.dentry->d_inode;
	int error = 0;

	/*
	 * O_APPEND cannot be cleared if the file is marked as append-only
	 * and the file is open for write.
	 */
	if (((arg ^ filp->f_flags) & O_APPEND) && IS_APPEND(inode))
		return -EPERM;

	/* O_NOATIME can only be set by the owner or superuser */
	if ((arg & O_NOATIME) && !(filp->f_flags & O_NOATIME))
		if (current->fsuid != inode->i_uid && !capable(CAP_FOWNER))
			return -EPERM;

	/* required for strict SunOS emulation */
	if (O_NONBLOCK != O_NDELAY)
	       if (arg & O_NDELAY)
		   arg |= O_NONBLOCK;

	if (arg & O_DIRECT) {
		if (!filp->f_mapping || !filp->f_mapping->a_ops ||
			!filp->f_mapping->a_ops->direct_IO)
				return -EINVAL;
	}

	if (filp->f_op && filp->f_op->check_flags)
		error = filp->f_op->check_flags(arg);
	if (error)
		return error;

	lock_kernel();
	if ((arg ^ filp->f_flags) & FASYNC) {
		if (filp->f_op && filp->f_op->fasync) {
            /* 从这里可以看到,调用了fasync函数
             * 那么控制权就交给的filp的驱动程序
             */
			error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
			if (error < 0)
				goto out;
		}
	}

	filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK);
 out:
	unlock_kernel();
	return error;
} // end of setfl

/* 驱动程序实现的fasync函数,从这里可以看到,主要是调用了
 * fasync_helper函数进行处理。
 * button_async函数是一个fasync_struct的结构体,
 * 可以暂时将其理解为一个存储信息的结构体(后面详细说明)
 */
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);
}

struct fasync_struct {
	int	magic;
	int	fa_fd;
	struct	fasync_struct	*fa_next; /* singly linked list */
	struct	file 		*fa_file;
};

接下来,我们对 fasync_helper(fd, filp, on, &button_async) 进行分析。

/* on 用于表示是否开启异步模式,开启时传入为正值(一个True值)
 * fapp 指向button_async结构体
 */
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) {
        /* 如果开启,则从内存中分配一块fasync_cache,用于存储button_async结构体 */
		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) {
		if (fa->fa_file == filp) {
            /* 如果文件描述符已经分配了存储空间,则将这次的fasync_cache进行释放 */
			if(on) {
				fa->fa_fd = fd;
				kmem_cache_free(fasync_cache, new);
			} else { /* 释放button_async的存储空间 */
				*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;
}

fifth_drv_fasyncfasync_helper 的代码,我们可以知道当应用程序设置一个 FASYNC 标志后,会在驱动程序创建一个 struct fasync_struct 的对象,那么这对象到底有什么用呢?这里我们就需要从中断触发后,执行的代码进行分析。

在韦东山的s3c2440开发板实现了一个驱动,主要关注是 kill_fasync() 函数。

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	struct pin_desc * pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;
	
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	if (pinval)
	{
		/* 松开 */
		key_val = 0x80 | pindesc->key_val;
	}
	else
	{
		/* 按下 */
		key_val = pindesc->key_val;
	}

    ev_press = 1;                  /* 表示中断发生了 */
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
	
    /* 当驱动发生后会执行buttons_irq函数,
     * 在这里我们通过kill_fasync (&button_async, SIGIO, POLL_IN)
     * 向应用程序发送一个SIGIO信号,要找到应用程序,
     * 就需要用button_async中存储的信息
     */
	kill_fasync (&button_async, SIGIO, POLL_IN);
	
	return IRQ_RETVAL(IRQ_HANDLED);
}

void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
	/* First a quick test without locking: usually
	 * the list is empty.
	 */
	if (*fp) {
		read_lock(&fasync_lock);
		/* reread *fp after obtaining the lock */
		__kill_fasync(*fp, sig, band); /* 主要是这个函数 */
		read_unlock(&fasync_lock);
	}
}

void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{
	while (fa) {
		struct fown_struct * fown;
		if (fa->magic != FASYNC_MAGIC) {
			printk(KERN_ERR "kill_fasync: bad magic number in "
			       "fasync_struct!\n");
			return;
		}
        /* 这个f_owner是我们调用fcntl(fd, F_SETOWN, getpid())
         * 设置进文件对象fa_file中的
         */
		fown = &fa->fa_file->f_owner;
		/* Don't 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;
	}
}

void send_sigio(struct fown_struct *fown, int fd, int band)
{
	struct task_struct *p;
	enum pid_type type;
	struct pid *pid;
	
	read_lock(&fown->lock);
	type = fown->pid_type;
	pid = fown->pid;
	if (!pid)
		goto out_unlock_fown;
	
	read_lock(&tasklist_lock);
	do_each_pid_task(pid, type, p) {
        /* 从这里我们可以看到,send_sigio_to_task对进程发送了信号
         * (内核用task来描述进程)
         * 我们不难想象,这个信号会最终导致应用程序用signal注册的信号处理函数被调用
         */
		send_sigio_to_task(p, fown, fd, band);
	} while_each_pid_task(pid, type, p);
	read_unlock(&tasklist_lock);
 out_unlock_fown:
	read_unlock(&fown->lock);
}

至此,我们就完成了对整个异步驱动程序执行流程的分析。

总结一下:

  1. 当应用程序调用 open 打开驱动程序创建设备时,内核会为这个进程创建一个文件描述符,用于在进程中修饰这个设备。
  2. 接着进程调用 fcntl(fd, F_SETOWN, getpid()) 将文件描述符的 f_owner 设置为当前进程的信息。
  3. 然后进程调用 fcntl(fd, F_SETFL, Oflags | FASYNC) 最终会调用驱动程序的 .fasync 函数,这个函数中会创建一个 fasync_struct 的数据结构,用于存储文件描述信息。
  4. 当中断触发后,中断程序会被调用,中断程序通过 kill_fasync() 使用 fasync_struct 内存储的信息,向文件描述符所有者——应用程序发送信号,最终应用程序捕获这个信号,然后执行 signal 中注册的信号处理程序。

驱动程序源码

你可能感兴趣的:(驱动开发,linux,驱动开发,学习)