Linux内核大讲堂 (二) 传说中的字符设备(4)
经过前面的学习,我们发现有一个东西像恶梦一样挥之不去,无论是讲驱动模型中的sysfs还是讲字符驱动的file,这些文件系统内的概念和模块已经让我们达到了无法忍受的地步,但这从侧面也说明了文件系统在内核中的至高地位。Linux宣称一切皆文件,是不是一切皆文件并不是我们目前讨论的内容,就算偶尔有一些东西没有抽象成文件,但这不重要,也不影响文件这个概念在linux内核中的地位。如果我们把驱动模型的解理看成是任脉,那么毫无疑问虚拟文件系统就是督脉,任督二脉一通,小周天就形成,就可以外气内收,内气外放,就算跨过了内家功夫修行的第一道门槛。我看过黄日华版、胡军版的天龙八部,其中印象最深的就是北乔峰和南慕容。南慕容一身所学无数,号称以彼之道还施彼身,所学广博无人能出其右,但经常打不赢,我们的乔峰乔大侠拿得出手的武功好像就降龙十八掌,但就这一招,确是所向披靡,除了败在无名扫地僧手下之外,可以说没吃过败仗。我想这个中的原因肯定是因为乔峰大侠精通驱动模型和虚拟文件系统,而慕容大侠肯定是一天到晚的照猫画虎的写完网卡驱动写触摸屏驱动,写完触摸屏驱动写显示屏驱动...^_^。扯远了,其实说这些无非就是想说明虚拟文件系统的重要性,其实在我看来虚拟文件系统的产生是一种必然现象,只要开发者脑子没问题,当他自已的系统需要同时兼容多种文件系统的时候,就肯定会想到提取其共性,形成一个中间层。很明显在linux中这个中间层就是虚拟文件系统。不过虚拟文件系统远不是一个简单的中间层,后面我们会仔细分析虚拟文件系统及各种具体文件系统。
我们还是继续采用采用情境分析法。以一个具体的实例分析来切入。我们前两章都是讲的驱动相关的东西,这一章就踩在前两章的肩膀上来讲,以mknod和open来分析虚拟文件系统和ext3文件系统中的一小部分。可以说这一章是讲的“设备驱动的文件系统”。
前面我们是通过linux内核的函数在驱动中创建设备的,这一节我们通过mknod来创建一个字符设备,mknod还可以创建字符设备、块设备或等。我们就以字符设备为例吧。我们假设输入的命令是:mknod wwhs_chardev c 247 0。
mknod通过系统调用机制的处理后会跑到sys_mknod()。系统调用不清楚的同志请参照<<linux内核大讲堂(2)传说中的字符设备(3)>>有同志反映说没有路径找不到,所以我以后会加上路径了。^_^
linux-2.6.38.6/fs/namei.c
SYSCALL_DEFINE3(mknod, const char __user *, filename, int, mode, unsigned, dev)
{
return sys_mknodat(AT_FDCWD, filename, mode, dev);
}
SYSCALL_DEFINE4(mknodat, int, dfd, const char __user *, filename, int, mode,
unsigned, dev)
{
int error;
char *tmp;
struct dentry *dentry;
struct nameidata nd;
if (S_ISDIR(mode))
return -EPERM;
error = user_path_parent(dfd, filename, &nd, &tmp);
if (error)
return error;
dentry = lookup_create(&nd, 0);
if (IS_ERR(dentry)) {
error = PTR_ERR(dentry);
goto out_unlock;
}
if (!IS_POSIXACL(nd.path.dentry->d_inode))
mode &= ~current_umask();
error = may_mknod(mode);
if (error)
goto out_dput;
error = mnt_want_write(nd.path.mnt);
if (error)
goto out_dput;
error = security_path_mknod(&nd.path, dentry, mode, dev);
if (error)
goto out_drop_write;
switch (mode & S_IFMT) {
case 0: case S_IFREG:
error = vfs_create(nd.path.dentry->d_inode,dentry,mode,&nd);
break;
case S_IFCHR: case S_IFBLK:
error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,
new_decode_dev(dev));
break;
case S_IFIFO: case S_IFSOCK:
error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,0);
break;
}
out_drop_write:
mnt_drop_write(nd.path.mnt);
out_dput:
dput(dentry);
out_unlock:
mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
path_put(&nd.path);
putname(tmp);
return error;
}
因为我们在用mknod的时候是创建的字符设备,因此我们根据传入的mode可以知道会跳转到:
case S_IFCHR: case S_IFBLK:
error = vfs_mknod(nd.path.dentry->d_inode,dentry,mode,
new_decode_dev(dev));
让我们再来看看vfs_mknod为我们做了啥。
int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
{
int error = may_create(dir, dentry);
if (error)
return error;
if ((S_ISCHR(mode) || S_ISBLK(mode)) && !capable(CAP_MKNOD))
return -EPERM;
if (!dir->i_op->mknod)
return -EPERM;
error = devcgroup_inode_mknod(mode, dev);
if (error)
return error;
error = security_inode_mknod(dir, dentry, mode, dev);
if (error)
return error;
error = dir->i_op->mknod(dir, dentry, mode, dev);
if (!error)
fsnotify_create(dir, dentry);
return error;
}
这当中有这么一行:
error = dir->i_op->mknod(dir, dentry, mode, dev);
这个是指调用具体文件系统的mknod()函数。
我们可以在shell中输入df –hT查看当前使用的文件系统。
我的显示如下:
[root@localhost /]# df -hT
/dev/mapper/VolGroup00-LogVol00
ext3 28G 5.6G 21G 22% /
/dev/sda1 ext3 99M 23M 72M 25% /boot
tmpfs tmpfs 506M 0 506M 0% /dev/shm
由此可见是ext3。对应的函数明显就是:
static int ext3_mknod (struct inode * dir, struct dentry *dentry,
int mode, dev_t rdev)
{
handle_t *handle;
struct inode *inode;
int err, retries = 0;
if (!new_valid_dev(rdev))
return -EINVAL;
dquot_initialize(dir);
retry:
handle = ext3_journal_start(dir, EXT3_DATA_TRANS_BLOCKS(dir->i_sb) +
EXT3_INDEX_EXTRA_TRANS_BLOCKS + 3 +
EXT3_MAXQUOTAS_INIT_BLOCKS(dir->i_sb));
if (IS_ERR(handle))
return PTR_ERR(handle);
if (IS_DIRSYNC(dir))
handle->h_sync = 1;
inode = ext3_new_inode (handle, dir, mode);
err = PTR_ERR(inode);
if (!IS_ERR(inode)) {
init_special_inode(inode, inode->i_mode, rdev);
#ifdef CONFIG_EXT3_FS_XATTR
inode->i_op = &ext3_special_inode_operations;
#endif
err = ext3_add_nondir(handle, dentry, inode);
}
ext3_journal_stop(handle);
if (err == -ENOSPC && ext3_should_retry_alloc(dir->i_sb, &retries))
goto retry;
return err;
}
这当中最主要的函数就是init_special_inode()。
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
" inode %s:%lu/n", mode, inode->i_sb->s_id,
inode->i_ino);
}
看到了吗?
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
}
终于找到了与字符设备相关的 fops了。
这里就把字符设备的fops赋值给了inode。
让我们看看def_chr_fops。
const struct file_operations def_chr_fops = {
.open = chrdev_open,
.llseek = noop_llseek,
};
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj);
spin_lock(&cdev_lock);
/* Check i_cdev again in case somebody beat us to it while
we dropped the lock. */
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
ret = -ENXIO;
filp->f_op = fops_get(p->ops);
if (!filp->f_op)
goto out_cdev_put;
if (filp->f_op->open) {
ret = filp->f_op->open(inode,filp);
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
这下上一节没打通的经脉终于打通了。
一起来看看,首先调用kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx),记得我们上一堂课讲字符设备的时候有提到过cdev_map。这个东东包含了255个probes成员,我们的cdev就是被存在对应的probe对应的probes中的data成员中。data是一个void *类型的指针,啥都可以指的。不记得的同志回头看看。kobj_lookup()函数就是取出对应的kobj。
接下来我们可以看到又用到了container_of,预备唱:
结识新朋友,不忘老朋友…
怎么样,见到这哥们激动吧!^_^
通过new = container_of(kobj, struct cdev, kobj)我们得到对应的cdev。
if (!p) {
inode->i_cdev = p = new;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
显然这时候我们的p是空的,因为我们是第一次打开。所以我们会进入if域内并做相应的赋值。
然后我们进行了如下操作:
filp->f_op = fops_get(p->ops);
if (filp->f_op->open) {
ret = filp->f_op->open(inode,filp);
if (ret)
goto out_cdev_put;
}
这个open()就是我们最先在注册字符设备的时候注册的wwhs_open()。
至此这一条经脉已经打通了。
这就是文件系统对字符驱动的支持方式。后面的read、write就更简单了,自个玩一玩吧,就当练手,如果没搞明白就先回过头去看看,肯定可以看明白的。
通过我这个系列的讲解大家可以发现,有效的规避细节,可以迅速搞清楚我们想要搞明白的东西。这不是阿Q精神,这是应对大系统的一种非常有效的学习方法。如果你想在入门之初搞清楚每一个细节,可能一辈子都入不了门。我和很多高手交流过,甚至是一些所谓的大牛,感觉有部分同志确实是在稀里糊图写驱动,搞不清楚来龙去脉,但驱动的API函数确记得很清,内核变动后,就又到处查资料,到处问人,周而复始,乐此不疲。我也不知道到底是我的方法对还是人家的方法对,但就我个人的成长感受而言,我是真的越学越轻松,也越学越明白。大家具体选择哪一种方法,这个就不强求了。在整个学习的过程中我们可以发现linux内核中各模块是紧密相连的,就像我们伟大天朝的各司法部门紧密相连一样,你如果要办点事,肯定要先找个突破点,比如你的朋友或者其它熟人,然后利用这些熟人再做进一步的突破,否则你没摸清楚状况就乱搞,到头来可能适得其反。这种策略同样适用于linux内核的学习。在这里让我们感谢伟大的天朝:是你让我学会了一种思维方式,是你让我失去了一个成为垃圾软件工程师的机会。我感谢你八辈祖宗!^_^
好了,到此字符驱动的讲解已经告一段落了,字符驱动的讲解引入了系统调用和虚拟文件系统等概念,系统调用上一节大概讲的比较清楚了,接下来我们就要深入分析我们的督脉,虚拟文件系统了。感兴趣的朋友不要错过哦!^_^
本文来自****博客,转载请标明出处:http://blog.****.net/z2007b/archive/2011/06/13/6540330.aspx