如果写过设备驱动,就知道我们写驱动主要是为了实现一个设备驱动接口,一组
对设备操作的方法,我这里想简单地分析一下文件系统与设备驱动之间的接口。
先来看打开操作,我自己对文件系统也不是很了解,只知道在用户空间调用了open函数,
就会在内核中调用sys_open这个系统调用,原来的0.11内核都是通过int80x系统调用门
来实现的,不过这里似乎是直接调用的嘛,在include/asm-arm/unistd.h中
static inline long open(const char *file, int flag, int mode)
{
extern long sys_open(const char *, int, int);
return sys_open(file, flag, mode);
}
再来看fs/open.c中的sys_open函数
asmlinkage long sys_open(const char * filename, int flags, int mode)
{
char * tmp;
int fd, error;
#if BITS_PER_LONG != 32
flags |= O_LARGEFILE;
#endif
tmp = getname(filename);
fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd(); //获取一个未用的文件指示符
if (fd >= 0) {
struct file *f = filp_open(tmp, flags, mode);
//打开文件,建立file类型的结构并返回其指针
error = PTR_ERR(f);
if (IS_ERR(f))
goto out_error;
fd_install(fd, f);//把文件指示符和文件指针相关联
}
out:
putname(tmp);
}
return fd;
out_error:
put_unused_fd(fd);
fd = error;
goto out;
}
这样主要是在filp_open中实现了打开操作。
struct file *filp_open(const char * filename, int flags, int mode)
{
int namei_flags, error;
struct nameidata nd;
namei_flags = flags;
if ((namei_flags+1) & O_ACCMODE)
namei_flags++;
if (namei_flags & O_TRUNC)
namei_flags |= 2;
error = open_namei(filename, namei_flags, mode, &nd);
if (!error)
return dentry_open(nd.dentry, nd.mnt, flags);
return ERR_PTR(error);
}
关键是要知道是怎样得到file结构体的,首先当然是要读取设备文件对应的inode结构体
这是由函数open_namei实现的,然后dentry_open来建立file结构体。
struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags)
{
struct file * f;
struct inode *inode;
static LIST_HEAD(kill_list);
int error;
error = -ENFILE;
f = get_empty_filp(); //获取空的file结构体
if (!f)
goto cleanup_dentry;
f->f_flags = flags;
f->f_mode = (flags+1) & O_ACCMODE;
inode = dentry->d_inode;
if (f->f_mode & FMODE_WRITE) {
error = get_write_access(inode);
if (error)
goto cleanup_file;
}
f->f_dentry = dentry;
f->f_vfsmnt = mnt;
f->f_pos = 0;
f->f_reada = 0;
f->f_op = fops_get(inode->i_fop); //获取设备的file_operations结构
file_move(f, &inode->i_sb->s_files);
/* preallocate kiobuf for O_DIRECT */
f->f_iobuf = NULL;
f->f_iobuf_lock = 0;
if (f->f_flags & O_DIRECT) {
error = alloc_kiovec(1, &f->f_iobuf);
if (error)
goto cleanup_all;
}
if (f->f_op && f->f_op->open) { //执行设备驱动中的open操作
error = f->f_op->open(inode,f);
if (error)
goto cleanup_all;
}
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
return f;
cleanup_all:
if (f->f_iobuf)
free_kiovec(1, &f->f_iobuf);
fops_put(f->f_op);
if (f->f_mode & FMODE_WRITE)
put_write_access(inode);
file_move(f, &kill_list); /* out of the way.. */
f->f_dentry = NULL;
f->f_vfsmnt = NULL;
cleanup_file:
put_filp(f);
cleanup_dentry:
dput(dentry);
mntput(mnt);
return ERR_PTR(error);
}
不同的文件系统会使用不同的函数取获取inode结构,对于设备文件系统,我猜想在注册设备
建立节点的时候,其inode结构中的struct file_operations *i_fop成员变量就已经被赋值
为设备驱动的file_operations了。还是以/dev/console为例,其驱动注册的file_operations
是tty_fops,于是file结构中的f_op指向的就是tty_fops结构。于是代开操作就会调用到tty_fops
中的open函数。而对于普通文件,会有默认的file_operations结构,其open函数什么也不做。
对于用register_chrdev注册的设备,显然注册的时候驱动的file_operations结构的指针并没有
传给inode结构,因为其节点是可以在注册之前就建立的,其实对于字符设备在用mknod建立imode
时赋给的file_operations也是默认的def_chr_fops,在fs/devices.c中定义
static struct file_operations def_chr_fops = {
open: chrdev_open,
};
于是打开字符设备的时候都会执行chrdev_open
int chrdev_open(struct inode * inode, struct file * filp)
{
int ret = -ENODEV;
filp->f_op = get_chrfops(MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
if (filp->f_op) {
ret = 0;
if (filp->f_op->open != NULL) {
lock_kernel();
ret = filp->f_op->open(inode,filp);
unlock_kernel();
}
}
return ret;
}
ok,在chrdev_open中又用filp->f_op重新赋值,这一次赋给的是真正的设备的file_operations指针于是下此使用read,write系统调用的时候,通过filp->f_op调用的函数就是真正设备的read,write函数了。