Concurrency and competition (3) spin lock

The concept of spin lock

What is a spin lock?

 Spin lock is a lock mechanism proposed to protect shared resources, and it is also a common lock mechanism in the kernel. Spin locks resolve resource conflicts by "waiting in place". That is, after thread A acquires the spin lock, thread B also wants to acquire the spin lock. But thread B can't get it, so it can only "spin in place" (it still occupies the CPU and will not sleep), and keeps trying to get the spin lock until it succeeds, and then exits the loop.

There is only one toilet, A wants to go to the toilet, B is already in the toilet, so A can only wait outside for B, until B comes out after using the toilet, A can go in and go to the toilet.

API function of spin lock (1)

function describe
DEFINE_SPINLOCK(spinlock_t *lock) define and initialize a variable
int spin_lock_init(spinlock_t *lock) Initialize the spin lock
void spin_lock(spinlock_t *lock) Acquiring a spin lock, also known as locking
void spin_unlock(spinlock_t *lock) Release the spin lock, also called unlocking
int spin_trylock(spinlock_t *lock) Try to acquire the spinlock, if not, return 0
int spin_is_locked(spinlock_t *lock) Checks whether the spinlock is acquired, returns non-zero if not acquired, otherwise returns 0

Steps to use the spin lock

  1. Apply for a spin lock when accessing critical resources
  2. After acquiring the spin lock, it will enter the critical section, and if it cannot acquire the spin lock, it will "wait in place"
  3. Release the spin lock when exiting the critical section

Other spin lock API functions (2)

function describe
void spin_lock_irq(spinlock_t *lock) Disable interrupts and acquire spinlocks
void spin_unlock_irq(spinlock_t *lock) Turn on interrupts and release the spinlock
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags) Save the interrupt state, turn off the interrupt and acquire the spinlock
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) Restore the previously saved interrupt state, turn on the interrupt and release the spinlock
void spin_lock_bh(spinlock_t *lock) Close the bottom half, acquire a spinlock
void spin_unlock_bh(spinlock_t *lock) Open the lower half and acquire the spin lock

Notes on spin locks

  1. Because the spin lock will "wait in place", because "wait in place" will continue to occupy the CPU and consume CPU resources. Therefore, the lock time cannot be too long, that is, the code in the critical section cannot be too much.
  2. Functions that may cause the thread to sleep cannot be called in the critical section protected by the spin lock, otherwise a deadlock may occur.
  3. Spin locks are generally used on multi-core SOCs.

Examples of spinlocks in the kernel

insert image description here

spin lock deadlock

 In a multi-core CPU or a single-core CPU that supports preemption, the critical section protected by the spin lock cannot call any function that can cause sleep or block, otherwise a deadlock may occur.

 Using a spinlock prohibits preemption. For example, in a single-core CPU, after process A obtains the spin lock, the kernel preemption is temporarily disabled. If process A enters sleep at this time (giving up the right to use the CPU), process B also wants to obtain the spin lock at this time, but At this time, the spin lock is held by process A, and CPU preemption is prohibited at this time. Because it is a single core, process B cannot be scheduled out, and can only "spin in place" until the lock is released by A. But process A cannot run, and the lock cannot be released. A deadlock occurs.

 The above situation will not happen with multi-core CPU. Because other cores will schedule other processes.

 After process A acquires the spin lock, if an interrupt is generated and the shared resource is also accessed in the interrupt (the spin lock can be used in the interrupt), the spin lock cannot be obtained in the interrupt at this time, and it can only be "in place". Spin", resulting in a deadlock. To prevent this from happening, APIs such as spin_lock_irqsave can be used to disable interrupts and acquire spin locks.

write the code

Where is the critical section?

insert image description here

...
static spinlock_t spinlock;
static int flag = 1;

int misc_open(struct inode *inode,struct file *file)
{
    
    
	spin_lock(&spinlock);
	/***********临界区开始***********/
  
	/**********临界区结束***********/
	spin_unlock(&spinlock);

	printk("hello misc_open\n ");
	return 0;
}
...

As above, the area between adding the spin lock and unlocking the spin lock is the "critical area".

The simplest implementation logic

static spinlock_t spinlock;
static int flag = 1;

int misc_open(struct inode *inode,struct file *file)
{
    
    
	spin_lock(&spinlock);
	if(flag != 1) // 第一次程序进来不会有任何问题
	{
    
    
		spin_unlock(&spinlock);
		return -EBUSY;
	}
	flag = 0; // 第一次进来的程序会置该标志位,让后面的程序无法进来。
	spin_unlock(&spinlock);

	printk("hello misc_open\n ");
	return 0;
}

int misc_release(struct inode *inode,struct file *file)
{
    
    
	spin_lock(&spinlock);
	if(flag != 1)
	flag = 1;
	spin_unlock(&spinlock);
	
	printk("hello misc_relaease bye bye \n ");
	return 0;
}
  • As above, this is a driver of a device node. The first time program A opens the driver, it can be opened smoothly without any problem, and after opening, the global variable will be set, so that other programs will fail to open the driver again flag = 0. returns an error.
  • When program A runs out of the driver, it will open it flag = 1, so that the following programs can open the driver.
  • The logic is quite simple, that is, flagthe logic of operation and judgment must be executed in the critical section.

A special case:

  • Program A opens the driver. When the code runs to the point where the flag has not been fully assigned, program B also opens the driver. Due to the spinlock locking mechanism, it can only wait flag = 0; outside the critical section . spin_lock(&spinlock); //在这里等待Until program A runs through the critical section, the flag is also set to 1, and then program B still cannot open the driver! The purpose is achieved.

fully realized

led.c

#include <linux/init.h>         //初始化头文件
#include <linux/module.h>       //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h>   //包含了miscdevice结构的定义及相关的操作函数。
#include <linux/fs.h>           //文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/uaccess.h>      //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/io.h>           //包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/kernel.h>		//驱动要写入内核,与内核相关的头文件

#include <linux/atomic.h>
#include <asm/atomic.h> 

#define GPIO_DR 0xfdd60000     //LED物理地址,通过查看原理图得知
unsigned int *vir_gpio_dr;     //存放映射完的虚拟地址的首地址

static spinlock_t spinlock;
static int flag = 1;

int misc_open(struct inode *inode,struct file *file)
{
    
    
	spin_lock(&spinlock);
	if(flag != 1)
	{
    
    
		spin_unlock(&spinlock);
		return -EBUSY;
	}
	flag = 0;
	spin_unlock(&spinlock);

	printk("hello misc_open\n ");
	return 0;
}

int misc_release(struct inode *inode,struct file *file)
{
    
    
	spin_lock(&spinlock);
	flag = 1;
	spin_unlock(&spinlock);

	printk("hello misc_relaease bye bye \n ");
	return 0;
}

ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    
    
	printk("misc_read\n ");
	return 0;
}

ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
    
    	
    /*应用程序传入数据到内核空间,然后控制蜂鸣器的逻辑,在此添加*/
	// kbuf保存的是从应用层读取到的数据
    char kbuf[64] = {
    
    0};
    // copy_from_user 从应用层传递数据给内核层
	if(copy_from_user(kbuf,ubuf,size)!= 0) 
	{
    
    
        // copy_from_user 传递失败打印
		printk("copy_from_user error \n ");
		return -1;
	}
    //打印传递进内核的数据
    //printk("kbuf is %d\n ",kbuf[0]); 
	if(kbuf[0]==1) //传入数据为1 ,LED亮
	{
    
    
		*vir_gpio_dr = 0x80008000; 
	}
	else if(kbuf[0]==0) //传入数据为0,LED灭
		*vir_gpio_dr = 0x80000000;
	return 0;
}

//文件操作集
struct file_operations misc_fops={
    
    
	.owner = THIS_MODULE,
	.open = misc_open,
	.release = misc_release,
	.read = misc_read,
	.write = misc_write,
};
//miscdevice结构体
struct miscdevice  misc_dev = {
    
    
	.minor = MISC_DYNAMIC_MINOR,
	.name = "hello_misc",
	.fops = &misc_fops,
};
static int misc_init(void)
{
    
    
	int ret;
    //注册杂项设备
	ret = misc_register(&misc_dev);
	if(ret<0)
	{
    
    
		printk("misc registe is error \n");
	}
	printk("misc registe is succeed \n");
    //将物理地址转化为虚拟地址
	vir_gpio_dr = ioremap(GPIO_DR,4);
	if(vir_gpio_dr == NULL)
	{
    
    
	printk("GPIO_DR ioremap is error \n");
	return EBUSY;
	}
	printk("GPIO_DR ioremap is ok \n");	
	return 0;
}
static void misc_exit(void){
    
    
    //卸载杂项设备
	misc_deregister(&misc_dev);
	iounmap(vir_gpio_dr);
	printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m += led.o
KDIR =/home/liefyuan/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
	make -C  $(KDIR) M=$(PWD) modules modules ARCH=arm64 CROSS_COMPILE=/usr/local/arm64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
clean:
	rm -rf modules.order *.o workqueue.o  Module.symvers *.mod.c *.ko

Compile the module:

$ export ARCH=arm64
$ export CROSS_COMPILE=aarch64-linux-gnu-
$ make

Test application:

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    
    
	int fd;
	char buf[64] = {
    
    0};//定义buf缓存
	char val[1];
	//打开设备节点
	fd = open("/dev/hello_misc",O_RDWR);
	if(fd < 0)
	{
    
    
		//打开设备节点失败
		perror("open error \n"); 
		return fd;
	}
    sleep(10);
	close(fd);
	return 0;
}

app2.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    
    
	int fd;
	char buf[64] = {
    
    0};//定义buf缓存
	char val[1];
	//打开设备节点
	fd = open("/dev/hello_misc",O_RDWR);
	if(fd < 0)
	{
    
    
		//打开设备节点失败
		perror("open error \n"); 
		return fd;
	}
	//把缓冲区数据写入文件中
	while(1)
	{
    
    
		val[0] = 1;
		write(fd, val, sizeof(val));
		sleep(1);
		val[0] = 0;
		write(fd, val, sizeof(val));
		sleep(1);
	}
	close(fd);
	return 0;
}

Compile:

aarch64-linux-gnu-gcc app.c -o app.armelf
aarch64-linux-gnu-gcc app2.c -o app2.armelf

test:

[root@RK356X:/opt]# insmod led.ko
[  787.424679] misc registe is succeed
[  787.425141] GPIO_DR ioremap is ok

[root@RK356X:/opt]# ./app.armelf &

[root@RK356X:/opt]# ./app2.armelf
open error
: Device or resource busy
[root@RK356X:/opt]# ./app2.armelf [  805.822972] hello misc_open
[  805.822972]

[  815.824861] hello misc_relaease bye bye
[  815.824861]
[  818.127234] hello misc_open
[  818.127234]

  • Realized logic: After the program app is running, it will occupy the driver for 10 seconds. During these ten seconds, app2 cannot open the driver. It cannot be opened by app2 until the app releases the spin lock.

Guess you like

Origin blog.csdn.net/qq_28877125/article/details/128212982