当前位置:首页 > 嵌入式 > 嵌入式软件
[导读]做内核开发的朋友,可能对下面的代码都很眼熟。 1. static const struct file_operations xxx_fops = {2. .owner = THIS_MODULE,3. .llseek = no_llseek,4. .write = xxx_w

做内核开发的朋友,可能对下面的代码都很眼熟。

 

1. static const struct file_operations xxx_fops = {

2. .owner = THIS_MODULE,

3. .llseek = no_llseek,

4. .write = xxx_write,

5. .unlocked_ioctl = xxx_ioctl,

6. .open = xxx_open,

7. .release = xxx_release,

8. };

一般我们在xxx_open中会用类似如下的代码分配一块内存。

[cpp] view plain copy

1. file->private_data = kmalloc(sizeof(struct xxx), GFP_KERNEL);

然后在接下来的read/write/ioctl中,我们就可以通过file->private_data取到与此文件关联的数据。

最后,在xxx_release中,我们会释放file->private_data指向的内存。

如果只是上面这几种流程访问file->private_data所指向的数据,基本上不会出问题。

因为内核的文件系统框架已经做了很完善的处理。

对于迸发访问,我们自己也可以通过锁等机制来解决。

然而,我们通常还会在一些异步的流程中访问file->private_data所指向的数据,这些异步流程可能由定时器,中断,进程间通信等因素触发。

并且,这些流程访问数据时,没有经过内核的文件系统框架。

那么这就有可能导致出现问题了。

下面我们先来看看内核文件系统框架的部分实现代码,再来考虑如何规避可能出现的问题。我们的分析基于linux-3.10.102的内核源码。

首先,要得到一个fd,必须先有一次调用C库函数open的行为。而在C库函数open返回之前,其他线程得不到fd,当然也就不会对此fd进行操作。等拿到fd时,open操作都已经完成了。

实际上,更夸张的情况还是有可能存在的。例如,可能由于程序的错误甚至是程序员故意构造特殊代码,导致在open返回之前,其他线程就使用即将返回的fd进行文件操作了。这种情况,这里就不讨论了。有兴趣的朋友,可以自己钻研内核代码,看看会产生什么效果。

先看看文件打开操作的主要函数调用:

sys_open, do_sys_open, do_filp_open, fd_install, __fd_install。

安装fd的操作如下。可见这里是对文件表加了锁的,并且不是针对单个文件,是整体性的加锁。

[cpp] view plain copy

1. void __fd_install(struct files_struct *files, unsigned int fd,

2. struct file *file)

3. {

4. struct fdtable *fdt;

5. spin_lock(&files->file_lock);

6. fdt = files_fdtable(files);

7. BUG_ON(fdt->fd[fd] != NULL);

8. rcu_assign_pointer(fdt->fd[fd], file);

9. spin_unlock(&files->file_lock);

10. }

读写操作,代码结构非常相似。这里只看写操作吧。其实现如下:

[cpp] view plain copy

1. SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,

2. size_t, count)

3. {

4. struct fd f = fdget(fd);

5. ssize_t ret = -EBADF;

6.

7. if (f.file) {

8. loff_t pos = file_pos_read(f.file);

9. ret = vfs_write(f.file, buf, count, &pos);

10. file_pos_write(f.file, pos);

11. fdput(f);

12. }

13.

14. return ret;

15. }

[cpp] view plain copy

1. ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)

2. {

3. ssize_t ret;

4.

5. if (!(file->f_mode & FMODE_WRITE))

6. return -EBADF;

7. if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write))

8. return -EINVAL;

9. if (unlikely(!access_ok(VERIFY_READ, buf, count)))

10. return -EFAULT;

11.

12. ret = rw_verify_area(WRITE, file, pos, count);

13. if (ret >= 0) {

14. count = ret;

15. file_start_write(file);

16. if (file->f_op->write)

17. ret = file->f_op->write(file, buf, count, pos);

18. else

19. ret = do_sync_write(file, buf, count, pos);

20. if (ret > 0) {

21. fsnotify_modify(file);

22. add_wchar(current, ret);

23. }

24. inc_syscw(current);

25. file_end_write(file);

26. }

27.

28. return ret;

29. }

[cpp] view plain copy

1. ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)

2. {

3. struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };

4. struct kiocb kiocb;

5. ssize_t ret;

6.

7. init_sync_kiocb(&kiocb, filp);

8. kiocb.ki_pos = *ppos;

9. kiocb.ki_left = len;

10. kiocb.ki_nbytes = len;

11.

12. ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);

13. if (-EIOCBQUEUED == ret)

14. ret = wait_on_sync_kiocb(&kiocb);

15. *ppos = kiocb.ki_pos;

16. return ret;

17. }

可以看出,读写操作是无锁的。也不好加锁,因为读写操作,还有ioctl,有可能阻塞。如果需要锁,用户自己可以使用文件锁,《UNIX环境高级编程》中有关于文件锁的描述。

不过fdget与fdput中包含了一些rcu方面的操作,那是为了能够与close fd的操作迸发进行。[!--empirenews.page--]

另外,可以看出,如果只实现一个f_op->aio_write,也是可以支持C库函数write的。

再来看看ioctl的实现。

[cpp] view plain copy

1. SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)

2. {

3. int error;

4. struct fd f = fdget(fd);

5.

6. if (!f.file)

7. return -EBADF;

8. error = security_file_ioctl(f.file, cmd, arg);

9. if (!error)

10. error = do_vfs_ioctl(f.file, fd, cmd, arg);

11. fdput(f);

12. return error;

13. }

对于非常规文件,或者常规文件中文件系统特有的命令,最终都会走到

filp->f_op->unlocked_ioctl

另外,ioctl也是无锁的。同时,流程中包含了fdget与fdput,这一点与read/write一样。

再来看看关闭文件的操作。系统调用sys_close的实现如下(fs/open.c)

[cpp] view plain copy

1. SYSCALL_DEFINE1(close, unsigned int, fd)

2. {

3. int retval = __close_fd(current->files, fd);

4.

5. /* can‘t restart close syscall because file table entry was cleared */

6. if (unlikely(retval == -ERESTARTSYS ||

7. retval == -ERESTARTNOINTR ||

8. retval == -ERESTARTNOHAND ||

9. retval == -ERESTART_RESTARTBLOCK))

10. retval = -EINTR;

11.

12. return retval;

13. }

可见主要工作是__close_fd函数(fs/file.c)完成的,其代码如下。可见他是对进程的文件表加了锁的。因此,open、close操作是有互斥的,并且不是针对某一文件的互斥,而是整体的互斥。

对于close一个fd时,其他cpu上的线程若正要或正在读写此fd怎么办?可以看出,close操作并不会为此等待,而是直接继续操作。

其中的rcu_assign_pointer(fdt->fd[fd], NULL);清除了此fd与file结构的关联,因此在此之后通过此fd已经访问不到相应的file结构了。至于在此之前就发起了的且尚未结束的访问怎么处理,答案是在filp_close中处理。

[cpp] view plain copy

1. int __close_fd(struct files_struct *files, unsigned fd)

2. {

3. struct file *file;

4. struct fdtable *fdt;

5.

6. spin_lock(&files->file_lock);

7. fdt = files_fdtable(files);

8. if (fd >= fdt->max_fds)

9. goto out_unlock;

10. file = fdt->fd[fd];

11. if (!file)

12. goto out_unlock;

13. rcu_assign_pointer(fdt->fd[fd], NULL);

14. __clear_close_on_exec(fd, fdt);

15. __put_unused_fd(files, fd);

16. spin_unlock(&files->file_lock);

17. return filp_close(file, files);

18.

19. out_unlock:

20. spin_unlock(&files->file_lock);

21. return -EBADF;

22. }

filp_close又调用了fput, 后者的相关代码如下。可见当前任务若非内核线程,接下来就是走____fput,否则就是走delayed_fput。

但是最终都是走__fput,__fput中会调用file->f_op->release,即我们的xxx_release。

不过,从fput代码可以看出,____fput会由rcu相关的work触发。因此,可以预见当____fput被调用时,已经没有已经发生且尚未结束的针对此文件的访问流程了。

[cpp] view plain copy

1. static void ____fput(struct callback_head *work)

2. {

3. __fput(container_of(work, struct file, f_u.fu_rcuhead));

4. }

5.

6.

7. void flush_delayed_fput(void)

8. {

9. delayed_fput(NULL);

10. }

11.

12. static DECLARE_WORK(delayed_fput_work, delayed_fput);

13.

14. void fput(struct file *file)

15. {

16. if (atomic_long_dec_and_test(&file->f_count)) {

17. struct task_struct *task = current;

18.

19. if (likely(!in_interrupt() && !(task->flags & PF_KTHREAD))) {

20. init_task_work(&file->f_u.fu_rcuhead, ____fput);

21. if (!task_work_add(task, &file->f_u.fu_rcuhead, true))

22. return;

23. }

24.

25. if (llist_add(&file->f_u.fu_llist, &delayed_fput_list))

26. schedule_work(&delayed_fput_work);

27. }

28. }

现在再来想想,我们上面提到的那些访问file->private_data所指向的数据的异步流程,这些流程并没有走文件系统框架。

会不会出现这种情况,xxx_release已经执行过了,可是异步流程却还来访问file->private_data所指向的数据呢?

其实xxx_release不妨不要释放file->private_data指向的内存,而是标记一下他的状态为已关闭。然后异步流程再访问此数据时,先检查一下状态。

若为已关闭,则妥善处理并释放即可。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭