【notes11】并发与竞争

文章目录


1.锁机制

场景:进程与进程(单/多核),进程与中断,中断与中断。

使用锁保护临界区,如果使用不当,可能出现死锁(自旋锁)问题。内核里面的锁非常多,定位很难,为了方便定位死锁问题,内核提供了死锁检测工具lockdep。

2.信号量



2.1 案例

c 复制代码
// semaphore.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/semaphore.h>

struct semaphore semaphore_test;//定义一个semaphore类型的结构体变量semaphore_test
static int open_test(struct inode *inode,struct file *file)
{
    printk("\nthis is open_test \n");
    down(&semaphore_test);//信号量数量-1
    return 0;
}

static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{
    int ret;
    char kbuf[10] = "topeet";//定义char类型字符串变量kbuf
    printk("\nthis is read_test \n");
    ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用户空间传递的数据
    if (ret != 0){
        printk("copy_to_user is error \n");
    }
    printk("copy_to_user is ok \n");
    return 0;
}
static char kbuf[10] = {0};//定义char类型字符串全局变量kbuf
static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{
    int ret;
    ret = copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用户空间传递的数据
    if (ret != 0){
        printk("copy_from_user is error\n");
    }
    if(strcmp(kbuf,"topeet") == 0 ){//如果传递的kbuf是topeet就睡眠四秒钟
        ssleep(4);
    }
    else if(strcmp(kbuf,"itop") == 0){//如果传递的kbuf是itop就睡眠两秒钟
        ssleep(2);
    }
    printk("copy_from_user buf is %s \n",kbuf);
    return 0;
}
static int release_test(struct inode *inode,struct file *file)
{
    up(&semaphore_test);//信号量数量加1   
    printk("\nthis is release_test \n");
    return 0;
}

struct chrdev_test {
       dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号
       int major,minor;//定义int类型的主设备号major和次设备号minor
       struct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备
       struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
};
struct chrdev_test dev1;//创建chrdev_test类型的
struct file_operations fops_test = {
      .owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
      .open = open_test,//将open字段指向open_test(...)函数
      .read = read_test,//将read字段指向read_test(...)函数
      .write = write_test,//将write字段指向write_test(...)函数
      .release = release_test,//将release字段指向release_test(...)函数
};
 

static int __init atomic_init(void)
{
    sema_init(&semaphore_test,1);//初始化信号量结构体semaphore_test,并设置信号量的数量为1
    if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自动获取设备号,设备名chrdev_name
        printk("alloc_chrdev_region is error \n");
    }
    printk("alloc_chrdev_region is ok \n");
    dev1.major = MAJOR(dev1.dev_num);//使用MAJOR()函数获取主设备号
    dev1.minor = MINOR(dev1.dev_num);//使用MINOR()函数获取次设备号
    printk("major is %d,minor is %d\n",dev1.major,dev1.minor);
    cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到fops_test结构体
    dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函数进行字符设备的添加
    dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_test
    device_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使用device_create进行设备的创建,设备名称为device_test
    return 0;
}

static void __exit atomic_exit(void)
{
    device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备
    class_destroy(dev1.class_test);//删除创建的类
    cdev_del(&dev1.cdev_test);//删除添加的字符设备cdev_test
    unregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号
    printk("module exit \n");
}
module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");
shell 复制代码
# Makefile
export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m := semaphore.o
KDIR :=/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
    make -C $(KDIR) M=$(PWD) clean    #make clean操作
c 复制代码
// app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
 #include <unistd.h>
int main(int argc, char *argv[])
{
    int fd;//定义int类型的文件描述符
    char str1[10] = {0};//定义读取缓冲区str1
    fd = open(argv[1],O_RDWR);//调用open函数,打开输入的第一个参数文件,权限为可读可写
    if(fd < 0 ){
        printf("file open failed \n");
        return -1;
    }
    /*如果第二个参数为test,条件成立,调用write函数,写入test*/    
    if (strcmp(argv[2],"test") == 0 ){
        write(fd,"topeet",10);
    }
    /*如果第二个参数为te,条件成立,调用write函数,写入te*/  
    else if (strcmp(argv[2],"te") == 0 ){
        write(fd,"te",10);
    }
    close(fd); 
    return 0;
}

3.原子变量

4. 自旋锁

5.互斥锁

互斥锁和自旋锁选择:自旋锁(开销少)的自旋时间和被锁住的代码执行时间成正比关系






如下是字符设备节点名叫"chrdev_name"对应下面:1.module_init,2.对驱动里一些临界资源进行修改时持锁,改完放锁,3.module_exit。下面临界区很小就是flag=这一行。



自旋锁死锁:进程或线程因长久等待已被其他进程占有的资源而陷入阻塞的一种状态,死锁一旦发生,程序解决不了,只能重启或开门狗复位

因为要保存中断上下文要更多内存开销,性能损耗。




如下有很多成员,竞争者从调度器运行队列中删除,然后放入处于休眠状态下的等待队列,然后内核调度器执行其他任务,这是一个加锁的过程,等待队列是加锁时候会放入的,放锁就是等待队列中等待者被唤醒时候。

使用mutex_lock要非常小心,因为只有能够保证无论什么时候互斥锁都会被释放时候,我们才能使用mutex_lock,因此用户上下文中建议始终使用的是带interruptible获取互斥锁。

最后一个是原子上下文,不存在cpu切换





如下是不同步的表现。


c 复制代码
// hello_chr_locked.c
/*
	信号量: sema_init()    初始化信号量
		     down_interruptible()    获取信号量
		     up()    释放信号量

	互斥锁: mutex_init()
			 mutex_lock_interruptible()   获取
			 mutex_unlock()
*/
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/slab.h>
#include<linux/uaccess.h>
#include<linux/jiffies.h>
#include<linux/sched.h>
#include<linux/semaphore.h>  //信号量
#include<linux/mutex.h>      //互斥锁
#define LOCK_USE 1  //0:semaphore, 1:mutex
#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2

int hello_major = HELLO_MAJOR;
int hello_minor = 0;
dev_t devt;      //高12位是主设备号,低20位是次设备号
int hello_nr_devs = HELLO_NR_DEVS;

module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);
module_param(hello_nr_devs, int, S_IRUGO);

struct hello_char_dev{		//实际的字符设备结构,类似于面向对象的继承
	struct cdev cdev;
	char *c;
	int n;
	struct semaphore sema;   //信号量
	struct mutex mtx;       //互斥锁 ,实际两者中一个就行
};
struct hello_char_dev *hc_devp;
struct class *hc_cls;

//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
int hc_open(struct inode *inode, struct file *filp)
{
	struct hello_char_dev *hc_dev;
	printk(KERN_INFO "%s open \n",current->comm);
	hc_dev = container_of(inode->i_cdev,struct hello_char_dev,cdev);  //获取设备结构体的地址
	filp->private_data = hc_dev;		//将设备结构地址放到文件描述符结构的私有数据中

	return 0;
}

ssize_t hc_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
{
	ssize_t retval=0;
	struct hello_char_dev *hc_dev=filp->private_data;
	printk(KERN_INFO "read hc_dev %p\n",hc_dev);
	
	if(*f_pos >= hc_dev->n)
		goto out;
	if(*f_pos + count > hc_dev->n)
		count = hc_dev->n - *f_pos;
	
	if(copy_to_user(buf,hc_dev->c,count))
	{
		retval = -EFAULT;
		goto out;
	}

	*f_pos += count;
	return count;	
out:
	return retval;
}

ssize_t hc_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{
	struct hello_char_dev *hc_dev=filp->private_data;
	int retval = -ENOMEM;
	unsigned long jiff1;
	printk(KERN_INFO "%s write begin\n",current->comm);
	#if (LOCK_USE==0)
	if(down_interruptible(&hc_dev->sema))	 //-EINTR   down_interruptible获得信号量,成功获取返回0,执行后面操作。获取不到进行等待,让进程进入休眠状态。如果我们给它发送一个信号(不是信号量),它就会返回一个非0值,这个值就是EINTR,如果接到EINTR就返回ERESTARTSYS去重新获取信号量。如果接到信号后退出,下行就返回EINTR。
		return -ERESTARTSYS;
	printk(KERN_INFO "%s get sema\n",current->comm);
	#endif
	#if (LOCK_USE==1)
	if(mutex_lock_interruptible(&hc_dev->mtx))  //-EINTR  mutex_lock_interruptible获得互斥量,参数同样是互斥量地址,获得到互斥量返回0(不进这个if里)执行后面操作,无法获得互斥量就会让进程进入休眠状态,接收到一个信号返回非0值即-EINTR
		return -ERESTARTSYS; 
	printk(KERN_INFO "%s get mutex\n",current->comm);
	#endif

   //获得到信号量或互斥锁后就可正常执行如下写操作:
	kfree(hc_dev->c);
	hc_dev->c=NULL;
	hc_dev->n=0;
	printk(KERN_INFO"%s 1",current->comm);
	jiff1=jiffies;
	while(jiffies-jiff1<HZ);
	hc_dev->c = kzalloc(count,GFP_KERNEL);
	if(!hc_dev->c)
		goto out;
	printk(KERN_INFO"%s 2 addr:%p",current->comm,hc_dev->c);
	jiff1=jiffies;
	while(jiffies-jiff1<HZ);
	printk(KERN_INFO"%s 3 addr:%p",current->comm,hc_dev->c);
	if(copy_from_user(hc_dev->c,buf,count))
	{
		retval = -EFAULT;
		goto fail_copy;
	}
	hc_dev->n = count;
	
   //如上写操作执行完后,如下我们需要释放掉互斥量或互斥锁:
	#if (LOCK_USE==0)
	up(&hc_dev->sema);   //up释放信号量,参数是信号量地址
	printk(KERN_INFO "%s up sema\n",current->comm);
	#endif
	#if (LOCK_USE==1)
	mutex_unlock(&hc_dev->mtx);  //mutex_unlock释放互斥锁,参数是互斥锁地址
	printk(KERN_INFO "%s unlock mutex\n",current->comm);
	#endif
	printk(KERN_INFO"%s write done",current->comm);
	return count;	 
fail_copy:
	kfree(hc_dev->c);
out:
	#if (LOCK_USE==0)
	up(&hc_dev->sema);
	#endif
	#if (LOCK_USE==1)
	mutex_unlock(&hc_dev->mtx);
	#endif
	return retval;	 //不能返回0,否则会不停的写
}

int hc_release(struct inode *inode, struct file *filp)
{
	printk(KERN_INFO "%s release\n",current->comm);
	return 0;
}

struct file_operations hc_fops = {		//字符设备的操作函数
	.owner =    THIS_MODULE,
	.read =     hc_read,
	.write =    hc_write,
	.open =     hc_open,
	.release =  hc_release,
};

//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
static int __init hello_init(void)	
{
	int ret,i;
	printk(KERN_INFO "---BEGIN HELLO LINUX MODULE---\n");
	if(hello_major){
		devt=MKDEV(hello_major,hello_minor);
		ret=register_chrdev_region(devt,hello_nr_devs,"hello_chr");	//使用指定的设备号分配
	}
	else{
		ret = alloc_chrdev_region(&devt,hello_minor,hello_nr_devs,"hello_chr");//动态分配主设备号
		hello_major = MAJOR(devt);
	}
	if (ret < 0) {
		printk(KERN_WARNING "hello: can't get major %d\n", hello_major);
		goto fail;
	}
	
	hc_devp = kzalloc(sizeof(struct hello_char_dev)*hello_nr_devs,GFP_KERNEL);  //给字符设备分配空间,这里hello_nr_devs为2
	if(!hc_devp)
	{
		printk(KERN_WARNING "alloc mem failed");
		ret = -ENOMEM;
		goto failure_kzalloc;	// 内核常用goto处理错误
	}
	
	for(i=0;i<hello_nr_devs;i++){	
	#if (LOCK_USE==0)
		sema_init(&hc_devp[i].sema,1);  // 初始化信号量,第一个参数:信号量地址。第二个参数:信号量个数,这里设为1,1个资源,当一个进程获得,其他进程等待。
	 // 信号量可有多个,有一个进程来获取,信号量个数减1。直到信号量减到0,进程无法获取信号量。当其他进程释放信号量后,被休眠的进程才可以获得信号量。
	#elif (LOCK_USE==1)
		mutex_init(&hc_devp[i].mtx);   // 初始化互斥量, 没有个数问题,因为只有一个,一个进程获得互斥锁,其他进程只能等待。上面信号量初始化为1和这里互斥锁类似。
	#endif 

		cdev_init(&hc_devp[i].cdev,&hc_fops);		// 初始化字符设备结构
		hc_devp[i].cdev.owner = THIS_MODULE;
		ret = cdev_add(&hc_devp[i].cdev,MKDEV(hello_major,hello_minor+i),1);
		if(ret)
		{
			printk(KERN_WARNING"fail add hc_dev%d",i);
		}
	}	
	
	hc_cls = class_create(THIS_MODULE,"hc_dev");
	if(!hc_cls)
	{
		printk(KERN_WARNING"fail create class");
		ret = PTR_ERR(hc_cls);
		goto failure_class;
	}
	for(i=0;i<hello_nr_devs;i++){
		device_create(hc_cls,NULL,MKDEV(hello_major,hello_minor+i),NULL,"hc_dev%d",i);
	}	
	printk(KERN_INFO "---END HELLO LINUX MODULE---\n");
	return 0;

failure_class:
	kfree(hc_devp);
failure_kzalloc:		
	unregister_chrdev_region(devt,hello_nr_devs);
fail:
	return ret;	//返回错误,模块无法正常加载
}

static void __exit hello_exit(void)
{
	int i;	
	for(i=0;i<hello_nr_devs;i++)
	{
		device_destroy(hc_cls,MKDEV(hello_major,hello_minor+i));
	}
	class_destroy(hc_cls);
	for(i=0;i<hello_nr_devs;i++)
		cdev_del(&hc_devp[i].cdev);
	kfree(hc_devp);
	unregister_chrdev_region(devt,hello_nr_devs);	//移除模块时释放设备号		
	printk(KERN_INFO "GOODBYE LINUX\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等,没有内核会提示
MODULE_AUTHOR("KGZ");		//作者
MODULE_VERSION("V1.0");  	//版本

同理make后insmod,再运行writeloop和echo,dmesg。上面只是在写时加入互斥锁和信号量,读时也要加入。如下红线上面是writeloop,红线下面是bash,如下打印的get sema和up sema之间只有自己的1,2,3步。

相关推荐
mobai72 小时前
Ubuntu环境上安装NTP服务
linux·运维·ubuntu
郝学胜-神的一滴2 小时前
Linux Socket编程核心:深入解析sockaddr数据结构族
linux·服务器·c语言·网络·数据结构·c++·架构
小天源9 小时前
linux漏洞一键扫描
linux·运维·服务器·漏洞扫描
码农水水10 小时前
中国邮政Java面试被问:容器镜像的多阶段构建和优化
java·linux·开发语言·数据库·mysql·面试·php
wifi chicken11 小时前
Linux Wlan L3~L2封包逻辑详解
linux·网络·ping·封包
小白鸽i12 小时前
【LINUX】将源码驱动文件编译并生效
linux·运维·服务器
胡斌附体13 小时前
linux(ubuntu)拉取源码进行docker容器部署
linux·git·ubuntu·docker·node·nvm
wdfk_prog13 小时前
[Linux]学习笔记系列 -- [drivers][base]platform
linux·笔记·学习
永不复还14 小时前
linux 使用X11监听键盘鼠标输入
linux·x11