当前位置:首页 > 嵌入式 > 嵌入式软件
[导读]做内核开发的朋友,可能对下面的代码都很眼熟。 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指向的内存,而是标记一下他的状态为已关闭。然后异步流程再访问此数据时,先检查一下状态。

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

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

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭