【1114】【并发控制】【笔记】

溯源

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

并发带来的问题

竞争:使用同一寄存器,就会有冲突;

互斥:时间上等待

那些是并发执行的

争抢设备

  • 进城设置标志位
  • 中断随机打断,进程和中断访问设备时,(关中断
  • 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读拷贝更新

相关推荐
2035去旅行2 小时前
ADB的底层原理
linux·adb·嵌入式·arm·soc
武子康2 小时前
单卡 A6000 跑 Qwen-Drive:怎样留下可比较的第一条基线?
linux·人工智能·深度学习
女神下凡2 小时前
芯参谋(107):杰理AC_中科蓝讯_开发指南
linux·服务器·网络·单片机·嵌入式硬件
落羽的落羽2 小时前
【C++】C++17的主要特性
linux·服务器·开发语言·c++·算法·机器学习
爱和冰阔落6 小时前
【Linux网络】TCP 半包粘包怎么解决?从长度报头、JSON 拆包到网络计算器完整实战
linux·运维·网络·tcp/ip
say_fall8 小时前
【Linux网络编程】UDP套接字编程:系统编程组件、基础回显服务、InetAddr与地址转换、翻译服务与线程池聊天室
linux·服务器·网络·udp·vr
ö Constancy9 小时前
一台电脑的硬件组成与作用:按功能分类详解(Windows 与 Linux 视角)
linux·windows·电脑
江湖有缘9 小时前
基于Ubuntu部署SoduWork:企业级 AI 原生协同平台实战全流程
linux·人工智能·ubuntu·ai·soduwork
无奈笑天下18 小时前
银河麒麟上nproc命令教程详解
linux·运维·服务器