08 内核开发-避免冲突和死锁-mutex
课程简介:
Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础,让他们能够理解和参与到Linux内核的开发过程中。
课程特点:
1. 入门级别:该课程专注于为初学者提供Linux内核开发的入门知识。无论你是否具有编程或操作系统的背景,该课程都将从最基本的概念和技术开始,逐步引导学习者深入了解Linux内核开发的核心原理。
2. 系统化学习:课程内容经过系统化的安排,涵盖了Linux内核的基础知识、内核模块编程、设备驱动程序开发等关键主题。学习者将逐步了解Linux内核的结构、功能和工作原理,并学习如何编写和调试内核模块和设备驱动程序。
3. 实践导向:该课程强调实践,通过丰富的实例和编程练习,帮助学习者将理论知识应用到实际的Linux内核开发中。学习者将有机会编写简单的内核模块和设备驱动程序,并通过实际的测试和调试来加深对Linux内核开发的理解。
4. 配套资源:为了帮助学习者更好地掌握课程内容,该课程提供了丰富的配套资源,包括教学文档、示例代码、实验指导和参考资料等。学习者可以根据自己的学习进度和需求,灵活地利用这些资源进行学习和实践。
无论你是计算机科学专业的学生、软件工程师还是对Linux内核开发感兴趣的爱好者,Linux内核开发入门课程都将为你提供一个扎实的学习平台,帮助你掌握Linux内核开发的基础知识,为进一步深入研究和应用Linux内核打下坚实的基础。
这一讲,主要分享如何在内核开模块开发中如何避免冲突和死锁。
1.定义
冲突是指两个或多个处理器或线程争夺同一资源(例如内存位置或I / O设备)的情况。
死锁是指两个或多个处理器或线程等待彼此释放资源才能继续的情况。
2.内涵
避免冲突和死锁非常重要,因为它可以防止并发系统出现意外的行为。通过使用锁或信号量等技术,可以确保共享资源被安全地访问,并且处理器或线程不会无限期地等待。
避免冲突和死锁的一种常见技术是使用互斥锁 mutex。
互斥锁是一种机制,允许一次只有一个处理器或线程访问共享资源。
当一个处理器或线程想要访问共享资源时,它必须首先获取该资源的互斥锁。
如果该资源已经被另一个处理器或线程锁定,则请求该互斥锁的处理器或线程必须等待,直到该资源被解锁。
在内核开发中,互斥锁通常用于保护临界区。临界区是代码的一部分,只能由一个处理器或线程同时执行。
例如,如果多个处理器或线程同时尝试修改共享数据结构,则可能会导致数据损坏。
为了防止这种情况发生,可以将对共享数据结构的访问放在一个临界区内,并使用互斥锁来保护该临界区。
3.使用示例
以下是如何在内核开发中使用互斥锁来避免冲突和死锁的示例:
// 定义一个互斥锁
static DEFINE_MUTEX(my_mutex);
// 在进入临界区之前获取互斥锁
mutex_lock(&my_mutex);
// 在临界区内访问共享资源
// 离开临界区后释放互斥锁
mutex_unlock(&my_mutex);
通过使用互斥锁,可以确保一次只有一个处理器或线程访问共享资源,从而避免冲突和死锁。
4.具体代码实践
/*******编写hello.c ,Makefile 参考第一节基本*******/
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/printk.h>
static DEFINE_MUTEX(mymutex);
// init 接口
static int __init example_mutex_init(void)
{
int ret;
pr_info("mutex_example init\n");
ret = mutex_trylock(&mymutex);
if (ret != 0) {
pr_info("mutex is locked\n");
if (mutex_is_locked(&mymutex) == 0)
pr_info("mutex failed to lock!\n");
mutex_unlock(&mymutex);
pr_info("mutex is unlocked\n");
} else
pr_info("Failed to lock\n");
return 0;
}
// exit 接口
static void __exit example_mutex_exit(void)
{
pr_info("mutex_example exit\n");
}
module_init(example_mutex_init);
module_exit(example_mutex_exit);
MODULE_DESCRIPTION("Mutex example");
MODULE_LICENSE("GPL");
/******* Makefile******/
obj-m += hello.o
CFLAGS := -Wall -O2
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
5.运行结果
# 编译
peach@peach-VirtualBox:~/MutexModule$ make
make -C /lib/modules/5.15.0-105-generic/build M=/home/peach/MutexModule modules
make[1]: Entering directory '/usr/src/linux-headers-5.15.0-105-generic'
CC [M] /home/peach/MutexModule/hello.o
MODPOST /home/peach/MutexModule/Module.symvers
CC [M] /home/peach/MutexModule/hello.mod.o
LD [M] /home/peach/MutexModule/hello.ko
BTF [M] /home/peach/MutexModule/hello.ko
Skipping BTF generation for /home/peach/MutexModule/hello.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-105-generic'
# 运行
#dmesg
[ 459.961972] hello: loading out-of-tree module taints kernel.
[ 459.962016] hello: module verification failed: signature and/or required key missing - tainting kernel
[ 459.962140] example_mutex init
[ 459.962141] mutex is locked
[ 459.962142] mutex is unlocked
6.注意事项
- 性能开销:获取和释放互斥锁会产生性能开销。因此,请仅在需要时才使用互斥锁。
- 死锁:互斥锁可能会导致死锁,如果多个线程等待同一个互斥锁时,而该互斥锁被另一个线程持有。为了避免死锁,请确保线程不会无限期地等待互斥锁。
- 优先级反转:如果低优先级的线程获取了互斥锁,而高优先级的线程正在等待该互斥锁,则可能会发生优先级反转。为了避免优先级反转,请使用可抢占的互斥锁或小心管理互斥锁的使用。
7.最佳实践
- 使用可抢占的互斥锁:可抢占的互斥锁允许高优先级的线程抢占低优先级的线程持有的互斥锁。这有助于避免优先级反转。
- 小心管理互斥锁的使用:仅在需要时才获取互斥锁,并且在不再需要时立即释放互斥锁。避免在临界区内进行长时间的操作。
- 使用嵌套互斥锁:如果必须在嵌套的临界区中使用互斥锁,请使用嵌套互斥锁。嵌套互斥锁允许同一个线程多次获取同一个互斥锁,而不会导致死锁。
- 使用自旋锁:对于非常短的临界区,可以使用自旋锁代替互斥锁。自旋锁比互斥锁开销更小,但它们可能会导致 CPU 争用。
- 使用原子操作:对于非常简单的操作(例如更新一个计数器),可以使用原子操作代替互斥锁。原子操作是无锁的,因此它们不会导致死锁或优先级反转。
7.总结
本节主要讲解内核避免冲突和死锁的一种方式,其实,除了互斥锁之外,还有其他技术也可以用来避免冲突和死锁,如自旋锁和信号量。
但是,对于内核开发来说,互斥锁是最常用和最有效的方法,后面我们分节讲解这两种方式来避免死锁和冲突。