溯源
问题时好时坏,一般是有并发影响

并发带来的问题

竞争:使用同一寄存器,就会有冲突;
互斥:时间上等待
那些是并发执行的

争抢设备
- 进城设置标志位
- 中断随机打断,进程和中断访问设备时,(关中断
- cpu之间争抢
并发控制机制(互斥机制)

- 中断屏蔽:有中断参与时
- 自旋锁:cpu空转,上锁时间比较短时使用

- 互斥锁:标志位

- 信号量:
- 读写锁:不同时写,可以同时读
- 顺序锁:读不会被写堵塞
- 读拷贝更新:读的时候也能写
内核调度

软件模拟产生中断,停止当前的执行的,打断当前执行的

杀死某个进程

在应用层:
(安全性)
线程:是CPU调度和分派的基本单位
进程:程序的进行过程,以及相关资源的总称-->安全(独占资源,互不干扰)进程是线程容器

希望程序都相互独立,线程关联度比较大,所以用进程包裹起来
内核方便开发东西。把所有的内存、外存设备虚拟成资源,方便进程访问。不需要关心实际位置,只要知道地址就行。
虚拟设备:通过映射地址的关系
每个进程都有独立的地址空间,互不干扰。进程不能直接拷贝进行通讯。完全独立
线程之间可以共享进程的资源

操作系统文件系统提供0-4G空间


【总结】
并发交替执行,内核调度,分时复用
进程状态切换:PCB
应用层:app.c
内核层:led.c
线程两部分合在一体
本质就一个空间,为了安全
每个进程有独立的地址空间,有一个虚拟机
内核线程
bash
内核线程 //只运行在内核空间的进程 -> 内核里的后台执行
$ make
# insmod kthread_base.ko
# rmmod kthread_base
# ps -ef|more /*板子上查看
PID USER TIME COMMAND
1 0 0:03 {linuxrc} init 所有应用程序的父进程(1号) -> init进程
2 0 0:00 [kthreadd] 用于内核驱动创建的内核线程(2号) -> 守护进程
3 0 0:00 [ksoftirqd/0] ksoftirqd内核线程,当软中断高频时降为最低的内核线程来执行
5 0 0:00 [kworker/0:0H]
7 0 0:00 [rcu_preempt]
8 0 0:00 [rcu_sched]
9 0 0:00 [rcu_bh]
10 0 0:00 [migration/0]
11 0 0:00 [lru-add-drain]
12 0 0:00 [watchdog/0]
13 0 0:00 [cpuhp/0]
14 0 0:00 [cpuhp/1]
1900 0 0:00 [new a kthread] //new a kthread就是创建的内核线程
*/
内核调度:最简单的示例
内核线程如何创建:

内核空间,资源都是公用的,不分进程线程
执行后:

唤醒和停止

其他方法

启动:

ps -ef【查看进程有哪些】


下半部网络中断,当频繁处理的时候,会导致用户的task饿死。优先级
内核创建其他任务就是复制2号进程得到
软中断(软件模拟产生的中断)

驱动里创建内核线程
bash
//kthread_base.c
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/kthread.h>
struct task_struct *task_id = NULL;
int new_kthread(void *arg)
{
int n = 0;
while(1)
{
printk("%s: %d\n",__func__,n++);
ssleep(3); //ssleep是按秒睡眠 msleep是按毫秒睡眠
if(kthread_should_stop()) //接收停止信号
{
break;
}
}
return 0;
}
static int __init base_init(void)
{
printk("%s:\n",__func__);
task_id = kthread_create(new_kthread,NULL,"new a kthread"); //创建新内核线程
if(IS_ERR(task_id)){
printk("kthread_create err\n");
return -1;
}
wake_up_process(task_id); //唤醒内核线程(通过id指定)
/* 对比
kthread_run(new_kthread,NULL,"new and run a kthread"); //创建并运行内核线程(是kthread_create+wake_up_process)
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //1号进程 (内核启动方式)
kthread_create:
创建的内核线程有干净的上那上下文环境,适合于驱动模块或用户空间的程序创建内核线程使用,不会把某些内核信息暴露给用户程序
kernel_thread: 创建的进程可以是init或其他内核线程。而kthread_create:创建的进程的父进程被指定为kthreadd,
*/
return 0;
}
static void __exit base_exit(void)
{
printk("%s:\n",__func__);
kthread_stop(task_id); //停止内核线程(通过id指定)
}
module_init(base_init);
module_exit(base_exit);
软中断内核线程
软中断内核线程
bash
ksoftirqd 内核线程 // 高频中断时,把任务降为最底部的内核线程,避免普通进程被饿死
# cat /proc/softirqs /* 软中断:中断下半部
CPU0 CPU1 CPU2 CPU3
HI: 0 0 0 0
TIMER: 13904 2950 552 1969 定时器
NET_TX: 425 154 0 0 网络通讯用
NET_RX: 13618 0 0 0
BLOCK: 0 0 0 0
IRQ_POLL: 0 0 0 0
TASKLET: 34 0 0 0 中断下半部
SCHED: 12027 2662 289 1604 内核调度
HRTIMER: 0 0 0 0
RCU: 3153 1367 536 1261 RCU软中断(读-复制更新) 并发互斥
*/
start_kernel -> rest_init(); //其中创建了initd和kthreadd两个内核线程,且kthreadd处于休眠状态
early_initcall(spawn_ksoftirqd);
p = kthread_create(ksoftirqd, hcpu, "ksoftirqd/%d", hotcpu);
注 // ksoftirqd内核线程,是系统管理的, 用于解决 高频中断 普通进程被饿死问题
//而驱动里 kthread_create 创建的内核线程,是只能在内核空间运行的 后台服务(如刷新磁盘缓存等)
进程

进程内,线程共享资源
每一个进程都有一个独立的0-4G
虚拟化:帮我们进行隔离
虚拟内存
方便进程操作,
如何实现?怎么映射?

应用层有多个地址空间,进程之间的关系尽量简单
虚拟内存:逻辑地址到物理地址映射->独立统一的地址空间
逻辑地址=页码+页内偏移 (页码:在页表中查找页)
页:逻辑地址空间分成大小相等的区
页框(帧):内存物理地址空间分成大小相等的区,大小和页面相等
页表:页码于页框码构成的表格
MMU:内存地址映射单元
页码(逻辑地址)
页框码(内存地址)
快表:常用的放里面


一级页就是2、3级页的目录
内核把独立的地址空间虚拟成虚拟地址,每个进程都有一个0-4G,不需要考虑真实的地址,每个都是0-4G,降低进程间的耦合
进程控制块 PCB

bash
进程控制块 PCB //进程对象的描述 -> 进程调度控制
struct task_struct { //内核里只有任务(对应线程),没有进程
//工作线程拷贝主线程的task_struct,共用mm_struct
volatile long state; /*任务的状态
#define TASK_RUNNING 0 运行态或就绪态
#define TASK_INTERRUPTIBLE 1 可中断的休眠态
#define TASK_UNINTERRUPTIBLE 2 不可中断的休眠态
#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED) 暂停态
#define EXIT_ZOMBIE 32 僵死状态
#define EXIT_DEAD 16 死亡态
*/
pid_t pid; //线程id
pid_t tgid; //线程组id(进程ID)
struct mm_struct *mm //虚拟内存
}
PCB:进程控制块
task_struct:对象,也就是内核的线程
状态:宏定义
线程id
组id

进程树
bash
$ ps -eLf /* 查看进程和线程id
用户 进程ID 父进程ID 线程ID 线程数
UID PID PPID LWP C NLWP STIME TTY TIME CMD
root 1 0 1 0 1 6月09 ? 00:00:07 /sbin/init sp 所有应用程序的父进程(1号)-> init进程
root 2 0 2 0 1 6月09 ? 00:00:00 [kthreadd] 用于内核驱动创建的内核线程(2号) -> 守护进程
root 12 2 12 0 1 6月09 ? 00:00:03 [ksoftirqd/0] ksoftirqd内核线程
root 7827 1 7827 0 5 10:37 ? 00:00:00 /usr/libexec/ 同一进程下的多个线程
root 7827 1 7845 0 5 10:37 ? 00:00:00 /usr/libexec/
root 7827 1 7853 0 5 10:37 ? 00:00:00 /usr/libexec/
root 7827 1 7854 0 5 10:37 ? 00:00:00 /usr/libexec/
root 7827 1 7855 0 5 10:37 ? 00:00:00 /usr/libexec/
yhai 2348 2245 2348 0 1 10:54 pts/1 00:00:00 bash 终端进程
yhai 2354 2245 2354 0 1 10:54 pts/2 00:00:00 bash 终端进程 (用kill -9 2348 可杀死终端进程)
yhai 2383 2354 2383 0 1 10:57 pts/2 00:00:00 ps -eLf 用命令创建的进程,它的父进程是终端 2354
*/
1号进程

进程树:时间片轮转方式进行调度

线程才是运行的基本单位,进程只是个容器;
内核的并发控制
并发的问题
bash
例:实现设备只被一个进程打开
//验证测试
$ make
$ aarch64-linux-gnu-gcc app.c -o app
$ aarch64-linux-gnu-gcc app2.c -o app2
$ cp app app2 led.ko /nfs/rootfs
# insmod led.ko
# mknod /dev/led c 500 0
# ./app &
# ./app2 //希望 设备只被一个进程打开
一个进程实现灯闪烁,一个实现呼吸灯
只要保证一个设备只要一个进程打开就行
打开open实现互斥
加一个标志位:

效果:

设置busy的时候?被中断了怎么办???
互斥操作被中断怎么办?这段代码会在汇编编译成几条???
驱动程序
bash
//led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/io.h> //io操作的头文件(for ioremap readl)
#define LED_MA 500 //定义主设备号(区分不同的类别)
#define LED_MI 0 //次设备号
struct cdev cdev; //定义字符设备
#define LED_MAGIC 'L' //幻数
#define LED_ON _IOW(LED_MAGIC,1,int) //点亮灯
#define LED_OFF _IOW(LED_MAGIC,2,int) //灭灯
#define PINMUX_AUX_DAP4_SCLK_0 0x70003150 //管脚复用设置
#define GPIO3 0x6000D200 // 第3个Bank GPIO 的基地址
#define CNF 0x04 //配置寄存器 (0:GPIO 1:SFIO) 偏移量
#define OE 0x14 //输出使能寄存器 (1:使能 0:关闭)
#define OUT 0x24 //输出寄存器(1:高电平 0:低电平)
#define MSK_CNF 0x84 //配置屏蔽寄存器(高位1:屏蔽 高位0:不屏蔽 低位1:GPIO模式 低位0:SFIO模式)
#define MSK_OE 0x94 //输出使能屏蔽寄存器(高位1:禁止写 低位1:使能)
#define MSK_OUT 0xA4 //输出屏蔽寄存器(高位1:禁止写 低位1:高电平)
unsigned char *gpio_pinmux;
unsigned char * gpio_base;
int led_open (struct inode * inode, struct file * file){
printk(" led open go\n");
return 0;
}
int led_release(struct inode *inode, struct file * file){
printk(" led release go\n");
return 0;
}
void led_on(void)
{
writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //引脚输出高电平,点亮灯
printk("out put high ,led on 输出高电平,点亮灯\n");
}
void led_off(void)
{
writel(readl(gpio_base+OUT) & ~(1 << 7), gpio_base+OUT); //引脚输出低电平,灭灯
printk("out put low, led off 输出低电平,灭灯\n");
}
long led_ioctl(struct file *inode, unsigned int cmd , unsigned long args)
{
switch(cmd)
{
case LED_ON:
led_on();
break;
case LED_OFF:
led_off();
break;
default:
printk("no fond cmd =%d\n",cmd);
return -1;
}
return 0;
}
struct file_operations led_fops={//实现需要的文件操作
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
static int led_init(void)
{
int devno = MKDEV(LED_MA,LED_MI); //合并主次设备号,生成设备ID
int ret;
//1.注册设备号
ret = register_chrdev_region(devno,1,"yhai_led");
if(ret<0){
printk("register_chrdev_region fail \n");
return ret;
}
//2.初始化字符设备
cdev_init(&cdev,&led_fops); //字符设备的初始化
ret =cdev_add(&cdev,devno,1);
if(ret<0){
printk("cdev_add fail \n");
goto err1;
}
gpio_pinmux = ioremap(PINMUX_AUX_DAP4_SCLK_0,8);
if(gpio_pinmux ==NULL){
printk("ioremap gpio_pinmux faile \n");
goto err2;
}
writel((readl(gpio_pinmux) & ~(1 << 4))|1, gpio_pinmux);
gpio_base = ioremap(GPIO3, 0xFF);
if (gpio_base == NULL) {
printk("ioremap gpio_base error\n");
goto err3;
}
writel(readl(gpio_base+CNF) | 1 << 7, gpio_base+CNF); //配置引脚GPIO3_PJ.07 为 GPIO模式
writel(readl(gpio_base+OE) | 1 << 7, gpio_base+OE); //使能引脚(7号)
writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //输出高电平,点亮灯
writel(readl(gpio_base+MSK_CNF) | 1 << 7, gpio_base+MSK_CNF); //取消对GPIO模下引脚的屏蔽
writel(readl(gpio_base+MSK_OE) | 1 << 7, gpio_base+MSK_OE); //取消引脚 使能屏蔽
printk("led init yhai 1\n");
return 0;
//goto 实现 顺序申请,逆序释放
err3:
iounmap(gpio_pinmux);
err2:
cdev_del(&cdev);
err1:
unregister_chrdev_region(devno,1);
return ret;
}
static void led_exit(void)
{
int devno = MKDEV(LED_MA,LED_MI);
iounmap(gpio_pinmux);
iounmap(gpio_base);
cdev_del(&cdev);
unregister_chrdev_region(devno,1);
printk("led exit 6\n");
}
module_init(led_init); //模块加载入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明
应用程序一
bash
//app.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#define LED_MAGIC 'L' //幻数
#define LED_ON _IOW(LED_MAGIC,1,int) //点亮灯
#define LED_OFF _IOW(LED_MAGIC,2,int) //灭灯
int main(int argc, char **argv)
{
int fd;
int count =3;
fd = open("/dev/led", O_RDWR); //打开设备文件
if (fd < 0) {
perror("open");
exit(1);
}
while(count--)
{
printf("app go\n");
ioctl(fd, LED_ON); //发送控制命令 LED_ON
sleep(3);
ioctl(fd, LED_OFF); //发送控制命令 LED_OFF
sleep(3);
}
return 0;
}
应用程序二
bash
//app2.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#define LED_MAGIC 'L' //幻数
#define LED_ON _IOW(LED_MAGIC,1,int) //点亮灯
#define LED_OFF _IOW(LED_MAGIC,2,int) //灭灯
int main(int argc, char **argv)
{
int fd;
int count =3;
fd = open("/dev/led", O_RDWR); //打开设备文件
if (fd < 0) {
perror("open");
exit(1);
}
while(count--)
{
printf("app2 go\n");
ioctl(fd, LED_ON); //发送控制命令 LED_ON
sleep(1);
}
return 0;
}
原子操作
bash
原子操作(atomic) //变量的原子级操作 -> 不被打断,是一切互斥机制的基础
#include <asm/atomic.h>
static atomic_t led_atomic = ATOMIC_INIT(1); //定义原子变量,并初始化为1
int led_open (struct inode * inode, struct file * file){
if (!atomic_dec_and_test(&led_atomic)) { //自减一,并测试是否为零
atomic_inc(&led_atomic); //原子变量加一,恢复自减前状态
return - EBUSY; //已经被打开
}
printk(" led open go\n");
return 0;
}
int led_release(struct inode *inode, struct file * file){
atomic_inc(&led_atomic); //释放设备
printk(" led release go\n");
return 0;
}
互斥的语句:本身不能出问题;
atomic_dec_and_test:测试是不是为0?
最终是使用汇编指令实现的

中断屏蔽
bash
//中断屏蔽:关掉中断 -> 避免中断的争抢
#include <linux/interrupt.h>
local_irq_disable(); //屏蔽中断
//临界区: 访问共享资源的代码区域
//在改写串口寄存器时,因串口本身有数据不断触发中断的,
//可能导致写串口数据出错
serial_out(up, UART_IER, UART_IER_UUE); //见 drivers/tty/serial/pxa.c
uart_console_write(&up->port, s, count, serial_pxa_console_putchar);
local_irq_enable() //开中断
//常需保存当前的中断状态,便于恢复
用local_irq_save(flags); //代替local_irq_disable
用local_irq_restore(flags) //代替 local_irq_enable
//注:屏蔽中断的时间尽量短,否则会造成别的中断延时过久,甚至丢失,最好能采有屏蔽指定中断的方式
disable_irq(int irq); //屏蔽指定中断号的中断方式,可间隔比较久时间,
//程序员是某种情况下不需要用该中断才禁用的, 需要时再打开,给长时间关
//或知道某中断有争抢,只关闭该中断
enable_irq(int irq); //使能某中断号的中断
//例:键盘
gpio_keys_disable_button(){ //见 input/keyboard/gpio_keys.c
disable_irq(bdata->irq)
}
gpio_keys_enable_button(){
enable_irq(bdata->irq);
}
进程&中断都在用这个设备,怎么办???
中断&中断都在用这个设备,怎么办???
有中断参与争强的时候->
关中断
关所有中断:local_irq_disable,导致部分滞后延时,在同一个函数里就要马上开
关指定中断:disable_irq,不着急开

自旋锁
bash
自旋锁(spinlock): cpu自旋 死循环空转CPU 等待释放锁, 耗资源 -> 短,中断里,多cpu
#include <linux/spinlock.h>
static DEFINE_SPINLOCK(slock);
irq_handler(){ //适合于中断处理程序(spin_lock 不会睡眠)
spin_lock(&slock); //为避免很卡,可用 spin_try_lock()
writel(reg_addr,value); //临界区代码(时间尽量短,微秒级)
spin_unlock(&slock); //上锁后必须马上释放(函数内常配对出现)
}
//自旋锁 + 中断屏蔽 (避免中断争抢,或 临界区刚好调度切换,浪费CPU >10ms )
spin_lock_irqsave(&db->lock, flags);
iow(db, DM9000_EPAR, DM9000_PHY | reg);
iow(db, DM9000_EPCR, EPCR_ERPRR | EPCR_EPOS);
spin_unlock_irqrestore(&db->lock, flags);
两个代码都在读写同样寄存器,临界区代码

原理:

第一个使用lock设为0
第二个进来使用,lock=0,不满足,就被困在了while里,cpu在空转,忙等待
第三个进来也忙等待
使用自旋锁,时间尽量的短,读写一两个寄存器搞定就行
可以用于中断里,保护临界区的资源,不会进行睡眠
多cpu互斥,使用场景
- 中断场景里上锁:

- 第一个能进来,之后的都在死循环等待

spin_try_lock():尝试获得,没获得就退出了,不会等待
只能在一个函数利用,时间不能太久:

- spin_lock_irqsave:当上锁的时候发上时间片到时(内核中断的调度、切换),进行中断,就会出现问题。
互斥体
bash
互斥体(mutex): 二元变量 原子操作 能睡眠 -> 时间长(用得最多),
//互斥体是专门用来做互斥的, 和二元的信号量类似,
#include <linux/mutex.h>
static struct mutex lock; //定义互斥锁
mutex_init(&lock); // 初始化互斥锁
mutex_lock(&lock); //上锁 (无法获得,则阻塞睡眠)
mutex_unlock(&lock); //解锁
使用场景:
两个进程抢设备;就上锁

使用的是sleep睡眠,
这个进程在访问设备的时候,有锁,没拿到资源,就进行睡眠,cpu去下一个进程;
但是不能放在中断里;
睡眠:就可以睡眠很久,不同于自旋锁;不浪费cpu资源

第一个进程用完解锁,释放资源,下一个进程才能进入
是个原子操作,不担心被中断,但是不能放在中断函数里
信号量
bash
信号量(semaphore): 变量 原子操作 能睡眠 -> 时间长 互斥 同步(资源的计数)
#include <linux/semaphore.h>
static DEFINE_SEMAPHORE(semlock); //定义一个初始值为一的信号量
down(&semlock); //上锁 (无法获得,则阻塞睡眠)
up(&semlock); //解锁
互斥体只用于1个设备,只有0、1
信号量可以记录设备个数,只要初始化个数,down就减一,释放就是加1;

读写锁
bash
读写锁(rwlock): 分为读写,让读读不互斥 -> 细粒度互斥 -> 读多写少
动因 //分为读写,更细粒度的互斥。
//可多个同时读,但读写,写写 互斥
//大锁还是小锁: 互斥的粒度,对驱动上一把大锁,不如以更细粒度的,读写函数为单位进行互斥
场景 //读多写少(如内存 cache的访问)
实现 //基于自旋锁 -> 不睡眠,多CPU
#include <linux/rwlock.h>
rwlock_t lock; //定义锁
rwlock_init(&lock); //初始化锁
write_lock(&lock); //写锁(基于spin_lock)
//write_trylock try 不成立即返回
//write_lock_irqsave +中断屏蔽
//write_lock_bh +禁用中断下半部(local_bh_disable())
write_unlock(&lock);//释放写锁
read_lock(&lock); //读锁
read_unlock(&lock); //释放读锁
//例:led_driver 的open write. 被多个应用访问时,9个是read,一个是write时,
//如何提升效率,互斥意味等待,减少互斥,才能发挥并发的性能

内存有读有写,怎么实现内存互斥?
上锁,释放锁?同时间只有一个任务能进来;性能很低!!
没有写的时候;读可以同时读;
写写之间互斥
先写完再读;
之前的锁,只能保证一个任务在使用设备;
读多写少适合使用读写锁;
顺序锁:读写进行互斥;读优先??,写优先??
- 写优先;读一次,再进行重读;不一样的话就用第二次的数据
RCU:万人直播;读的性能不能太低
- 读优先;读、拷贝、更新
- 高频读,低频写
- 写的时候拷贝一份,修改副本,时机合适再更新,使用指针进行调整
示例:多个内核线程读写
bash
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/rwlock.h>
rwlock_t lock; //定义锁
struct task_struct *task1_id = NULL;
struct task_struct *task2_id = NULL;
struct task_struct *task3_id = NULL;
struct task_struct *task4_id = NULL;
int num = 1;
int thread_num=1;
int read_thread(void *arg)
{
int n = 0;
int id = thread_num++;
while(1)
{
read_lock(&lock); //读锁
printk("%s %d %d read num= %d \n",__func__,id,n++,num);
read_unlock(&lock); //释放读锁
ssleep(3);
if(kthread_should_stop()) //接收停止信号
{
break;
}
}
return 0;
}
int write_thread(void *arg)
{
int n = 0;
int id = thread_num++;
while(1)
{
write_lock(&lock); //读锁
num++;
printk("%s %d %d up num= %d \n",__func__,id,n++,num);
write_unlock(&lock); //释放读锁
ssleep(3);
if(kthread_should_stop()) //接收停止信号
{
break;
}
}
return 0;
}
static int __init base_init(void)
{
printk("%s:\n",__func__);
task1_id = kthread_run(read_thread,NULL,"thread1"); //创建内核线程
task2_id = kthread_run(read_thread,NULL,"thread2"); //创建内核线程
task3_id = kthread_run(write_thread,NULL,"thread3"); //创建内核线程
task4_id = kthread_run(write_thread,NULL,"thread4"); //创建内核线程
rwlock_init(&lock); //初始化锁
return 0;
}
static void __exit base_exit(void)
{
printk("%s:\n",__func__);
kthread_stop(task1_id); //停止内核线程(通过id指定)
kthread_stop(task2_id);
kthread_stop(task3_id);
kthread_stop(task4_id);
}
module_init(base_init);
module_exit(base_exit);
顺序锁
bash
顺序锁(seqlock):重读,弱化读写互斥 -> 只写写互斥(串行) -> 需写优先时
动因 //读写互斥等待问题,让写优先(等待少),采用把负担丢个读者(重读,冲突判断)
实现 //是基于自旋锁实现的,故不能睡眠
#include <linux/seqlock.h>
seqlock_t lock; //定义锁
seqlock_init(&lock); //初始化锁
write_seqlock(&lock); //写锁(源码用到 spin_lock)
//write_tryseqlock try 不成立即返回
//write_seqlock_irqsave +中断屏蔽
//write_seqlock_bh +禁用中断下半部(local_bh_disable())
write_sequnlock(&lock); //释放写锁
read_seqbegin(&lock); //读锁
read_seqretry(&lock); //重读,看有改写冲突没 -> 弱化 读写不互斥,提升读速度
//在顺序锁的一次读操作结束之后,调用顺序锁的重读函数,
//用于检查是否有写执行单元对共享资源进行过写操作;如果有,则需要重新读取共享资源
RCU读拷贝更新
