仅仅是分析时的记录........
假设有一个点灯的应用程序,这个程序所需要做的工作为:
1.读写文件
2.点灯,获取按键等等
在应用层有:
open read write (由c库实现)
驱动层有:
led_open led_read led_write
问题:应用层(open,read,write)是如何调用到底层驱动的led_open,led_read,led_write 等驱动函数的呢??
解释:
在驱动层和应用层之间有个系统调用接口:
sys_open sys_read sys_write,当应用层调用(open,read,write)函数时,会发出一个swi val的异常命令,系统调用接口会根据val的值判断异常的原因来调用相应的函数sys_open,sys_read,sys_write.这个函数会根据打开不同的文件节点来最终调用相应的底层驱动程序
下面分析下sys_open的流程:
在si中搜索sys_open,发现在arch\arm\kernel\calls.S下面有这个的形式:
CALL(sys_read)
CALL(sys_write)
/* 5 */ CALL(sys_open)
CALL(sys_close)
这个sys_open前面有个5的注释,猜测这个5就是前面所说的swi val中的val的值(
具体情况以后分析,这里只是猜测)
在这个文件中定义了300多个系统调用。
sys_open的定义如下:
asmlinkage long sys_open(const char __user *filename,
int flags, int mode);
问题:asmlinkage的意思是什么?
函数定义前加宏asmlinkage ,表示这些函数通过堆栈而不是通过寄存器传递参数。
gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定义的c函数时要在函数前加上宏asmlinkage
sys_open的实现如下:
asmlinkage long sys_open(const char __user *filename, int flags, int mode)
{
long ret;
if (force_o_largefile()) //查看打开的是否是大文件,如果是的话,置大文件标志位:O_LARGEFILE
flags |= O_LARGEFILE;
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
/* avoid REGPARM breakage on x86: */
prevent_tail_call(ret); //检查do_sys_open的调用返回值ret是否有效,ret也就是文件描述符。
return ret;
}
#define AT_FDCWD -100 /* Special value used to indicate openat should use the current working directory. */
AT_FDCWD:这是一个特殊的值用来指定openat应该使用当前目录,注释这样写的,看不懂。。。
do_sys_open的实现:
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
char *tmp = getname(filename); //主要功能是在使用文件名之前将其拷贝到内核数据区,正常结束时返回内核分配的空间首地址,出错时返回错误代码。
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd(); //取得系统中可用的文件描述符fd。
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, flags, mode);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f->f_path.dentry);
fd_install(fd, f);
}
}
putname(tmp);
}
return fd;
}
下面看下:
do_filp_open的实现:
static struct file *do_filp_open(int dfd, const char *filename, int flags,
int mode)
{
int namei_flags, error;
struct nameidata nd;
namei_flags = flags;
if ((namei_flags+1) & O_ACCMODE) // 如果flags有O_WRONLY,则增加O_RDONLY
namei_flags++;
error = open_namei(dfd, filename, namei_flags, mode, &nd); // open_namei函数主要执行文件操作的inode部分的打开等操作比如目录。
if (!error)
return nameidata_to_filp(&nd, flags);//把文件的inode相关信息转换成文件结构。
return ERR_PTR(error);
}
在分析之前,先看个struct nameidata 结构体:
struct nameidata {
struct dentry *dentry; //目录数据
struct vfsmount *mnt; // 虚拟文件挂载点数据
struct qstr last; // hash值
unsigned int flags; // 文件操作标识
int last_type; // 类型
unsigned depth;
char *saved_names[MAX_NESTED_LINKS + 1];
/* Intent data */
union {
struct open_intent open;
} intent;// 专用数据
};
struct file *nameidata_to_filp(struct nameidata *nd, int flags)
{
struct file *filp;
/* Pick up the filp from the open intent */
filp = nd->intent.open.file; // 把相关 file结构的指针赋予 filp。
/* Has the filesystem initialised the file for us? */
if (filp->f_path.dentry == NULL)
filp = __dentry_open(nd->dentry, nd->mnt, flags, filp, NULL); /* ***** 关键函数 ***** */
else
path_release(nd);
return filp;
}
__dentry_open:
static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,
int flags, struct file *f,
int (*open)(struct inode *, struct file *))
{
struct inode *inode;
int error;
f->f_flags = flags;
f->f_mode = ((flags+1) & O_ACCMODE) | FMODE_LSEEK |
FMODE_PREAD | FMODE_PWRITE;
inode = dentry->d_inode;//获取到inode,打开文件的inode,在open_namei做的赋值
if (f->f_mode & FMODE_WRITE) {
error = get_write_access(inode);
if (error)
goto cleanup_file;
}
f->f_mapping = inode->i_mapping;
f->f_path.dentry = dentry;
f->f_path.mnt = mnt;
f->f_pos = 0;
f->f_op = fops_get(inode->i_fop);//获取字符设备的fops
file_move(f, &inode->i_sb->s_files);
if (!open && f->f_op)
open = f->f_op->open; // // 在这里将open赋为chrdev_open。 // 此处获得 def_chr_fops
if (open) {
error = open(inode, f);//做chrdev_open操作
if (error)
goto cleanup_all;
}
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
file_ra_state_init(&f->f_ra, f->f_mapping->host->i_mapping);
/* NB: we're sure to have correct a_ops only after f_op->open */
if (f->f_flags & O_DIRECT) {
if (!f->f_mapping->a_ops ||
((!f->f_mapping->a_ops->direct_IO) &&
(!f->f_mapping->a_ops->get_xip_page))) {
fput(f);
f = ERR_PTR(-EINVAL);
}
}
return f;
cleanup_all:
fops_put(f->f_op);
if (f->f_mode & FMODE_WRITE)
put_write_access(inode);
file_kill(f);
f->f_path.dentry = NULL;
f->f_path.mnt = NULL;
cleanup_file:
put_filp(f);
dput(dentry);
mntput(mnt);
return ERR_PTR(error);
}
chrdev_open:
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);
// 执行kobj_lookup函数,在cdev_map里寻找相应的inode->i_rdev设备。
// cdev_map是一个256个probe结构组成的数组,用于查找具有相应设备号的设备。
// inode->i_rdev为设备号,主设备和次设备的结合。
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj);
/从kobj的位置倒算出cdev的内存地址,获得包含相应kobj的cdev。
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
// 到这里p已经为我们要的设备cdev了。
inode->i_cindex = idx;
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;
filp->f_op = fops_get(p->ops);
if (!filp->f_op) {
cdev_put(p);
return -ENXIO;
}
if (filp->f_op->open) {
lock_kernel();
ret = filp->f_op->open(inode,filp);
//拿到 cdev操作集。
// 至此以后read,write操作都通过file->f_op直接与我们要的设备操作集挂钩了。
unlock_kernel();
}
if (ret)
cdev_put(p);
return ret;
}
到此,系统通过file->f_op 就与我们在设备驱动里面的定义的相关操作联系起来了,我们之前在写驱动实现的功能操作就被系统通过应用层的open 一步一步的调用到我们自己的open跟相关其他的操作了。
下面看看底层如何注册字符设备的:
以一个按键为例:
register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);
/*
**BUTTON_MAJOR:主设备号
**DEVICE_NAME:设备的名字
**&s3c24xx_buttons_fops为button的底层操作函数结构体指针
*/
在分析这个函数之前先看一个结构体:
static struct char_device_struct {
struct char_device_struct *next;//这是一个链表
unsigned int major; //主设备号
unsigned int baseminor; //基本的次设备号
int minorct;
char name[64]; //设备的名字?
struct file_operations *fops; //设备的操作
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
register_chrdev() - Register a major number for character devices.这个函数的意思是注册一个注射被为major的字符设备,其实现如下:
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;
cd = __register_chrdev_region(major, 0, 256, name);//好像就是初始化一下char_device_struct,下面具体分析
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops; //将底层的操作函数附给struct cdev成员ops
kobject_set_name(&cdev->kobj, "%s", name);
for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
*s = '!';
err = cdev_add(cdev, MKDEV(cd->major, 0), 256);//将这个字符设备加载到系统中,下面分析其实现
if (err)
goto out;
cd->cdev = cdev; //将cdev附到char_device_struct上
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, 0, 256));
return err;
}
__register_chrdev_region的实现:
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);//分配空间
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
if (major == 0) {//如果设备的major==0(动态分配,在255个主设备id中找到一个未使用的)
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if (chrdevs[i] == NULL)
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
}
//对char_device_struct进行初始化
cd->major = major; //主设备号
cd->baseminor = baseminor; //次设备号的第一个id
cd->minorct = minorct; //次设备号最大支持的数目
strncpy(cd->name,name, 64); //设备的名字
i = major_to_index(major); //获取索引 == major % 255
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //遍历chrdev的链表,看看有无不符合条件的
if ((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
cd->next = *cp;
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
cdev_add的实现:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);//将cdev加入到系统中,下面分析实现:
}
kobj_map的实现:
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe; //这个p->get == exact_match,用于获取指定字符设备的kobj
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data; //指定设备的cdev的信息
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
struct kobj_map结构体:
struct kobj_map {
struct probe {
struct probe *next;//次设备链表
dev_t dev;//由主设备号和次设备号共同组成,每个设备都有单独的dev
unsigned long range;
struct module *owner;
kobj_probe_t *get; //这个参数会在进行系统调用的时候被使用到
int (*lock)(dev_t, void *);
void *data;
} *probes[255]; //这个kobj_map域中可以指示256个次设备的存放
struct mutex *lock;
};
到这里字符设备的注册就已经完成了:
总结一下:
1.初始化char_device_struct,
2. 定义一个 kobj_map 结构的 cdev_map 变量,内核中所有都字符设备都会记录在这个变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。
搜索关键字:从用户态的open到内核驱动实现流程
参考文档: