linux内核同步-2

上篇文章我们简单的介绍了linux内核同步中的原子操作,这篇文章我们继续看看linux内核提供的其他同步操作。

简单回顾一下原子操作

  • 原子操作只是保护对变量或位的操作。
  • 临界区同一时间,只有一个任务的代码区域,可以跨越多个函数。
  • 举个例子,我们经常会碰到这种情况:
    • 先得从一个数据结构中移出数据
    • 对其进行格式转换和解析
    • 最后再把它加入到另一个数据结构中
    • 整个执行过程必须是原子的,在数据被更新完毕前,不能有其他代码读取这些数据。
  • 显然,简单的原子操作对此无能为力,这就需要使用更为复杂的同步方法——锁来提供保护。

自旋锁

  • 任何时候只能有一个线程持有的锁
  • 注意:自旋锁被一个线程持有时,其他线程不能获得这个锁,只能忙等这个锁,
  • 长时间占有自旋锁并不是一个明智的方法
  • 接口定义在文件<linux/spinlock.h>中
    自旋锁的使用:
DEFINE_SPINLOCK(x);
spin_lock (&mr_lock);
//临界区
spin_unlock (&mr_lock);
  • 自旋锁不可递归
    • 如果已经获得自旋锁,然后再次试图获得将会导致死锁
  • 用于中断处理程序时要注意:
    • 首先需要禁止本地中断
    • 否则可能会试图两次获得自旋锁
    • 有一个很方便的接口禁止本地中断的同时获得自旋锁:
    • spin_lock_irqsave() 保存当前的中断状态,禁止本地中断,获得锁。
    • spin_unlock_irqrestore()对指定的锁解锁,然后让中断恢复到加锁前的状态
      典型的用法如下:
 DEFINE_SPINLOCK(mr_lock)
unsigned long flags; 
spin_lock_irqsave(&mr_lock, flags); 
/* 临界区*/ 
spin_unlock_irqrestore(&mr_lock, flags); 
  • 如果你能确定中断在加锁前是激活的,那就不需要在解锁后恢复中断以前的状态了。你可以无条件地在解锁时激活中断。
    spin_lock_irq()直接禁止中断,而不保存之前的状态
    spin_unlock_irq() 直接激活中断
    用法如下:
DEFINE_SPINLOCK(x);
spin_lock_irq(&mr_lock);
/*临界区…*/
spin_unlock_irq (&mr_lock);

自旋锁相关的方法如下表所示:



例子:
led_drv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <asm/atomic.h>
#include <linux/spinlock.h>


static unsigned long gpm4con;
#define GPM4CON (*(volatile unsigned long *)(gpm4con + 0x0))
#define GPM4DAT (*(volatile unsigned long *)(gpm4con + 0x04))


//open
//int led_flag = 0;
spinlock_t lock;

ssize_t led_open(struct inode *inop,struct file *filp)
{
    GPM4CON = 0X1111;
    GPM4DAT &= ~0XF;
    printk("led open!\n");
    return 0;
}

//release
ssize_t led_release(struct inode *inop,struct file *filp)
{
    GPM4DAT |= 0xf;
    printk("led release!\n");
    return 0;
}


static unsigned char led_state = 0;
//read
ssize_t led_read(struct file *filp,char __user *buf,size_t size,loff_t f_pos)
{
    spin_lock(&lock);
    copy_to_user(buf,&led_state,size);
    printk("led read data %d\n",led_state);
    spin_unlock(&lock);
    return size;
}

//write
ssize_t led_write(struct file *filp,const char __user *buf,size_t size,loff_t f_pos)
{
    spin_lock(&lock);
    copy_from_user(&led_state,buf,size);
    printk("led write data %d\n",led_state);
    GPM4DAT=led_state;
    spin_unlock(&lock);
    return size;

}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .read = led_read,
    .write = led_write,
};


static unsigned int led_major = 0;
static struct class *led_class;
#define LED_DRV_NAME "led_drv"

static int __init led_init(void)
{
    spin_lock_init(&lock);
    gpm4con=(unsigned long)ioremap(0x110002e0,12);
    led_major=register_chrdev(0,LED_DRV_NAME,&fops);
    led_class=class_create(THIS_MODULE,LED_DRV_NAME);
    device_create(led_class,NULL,MKDEV(led_major,0),NULL,LED_DRV_NAME);
    return 0;
}

static void __exit led_exit(void)
{
    device_destroy(led_class,MKDEV(led_major,0));
    class_destroy(led_class);
    iounmap((unsigned long *)gpm4con);
    unregister_chrdev(led_major,LED_DRV_NAME);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

led1.c

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

#define FILE_NAME "/dev/led_drv"

int main(void)
{
    int fd = 0;
    int i = 10; 
    fd = open(FILE_NAME,O_RDWR);
    sleep(1);
    printf("led1 open ok!\r\n");    
    while(i--)
    {
        printf("led1 write:\r\n");
        usleep(2);
        write(fd,&i,sizeof(i));
        sleep(1);
    }

    close(fd);
    return 0;
}

led2.c

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

#define FILE_NAME "/dev/led_drv"

int main(void)
{
    int fd = 0;
    int i = 0;  
    fd = open(FILE_NAME,O_RDWR);
    sleep(1);
    printf("led2 open ok!\r\n");    
    while(i<10)
    {
        printf("led2 write:\r\n");
        usleep(3);
        write(fd,&i,sizeof(i));
        sleep(1);
        i++;
    }

    close(fd);
    return 0;
}


makefile


obj-m += led_drv.o
all:
    make -C /home/sice/linux-3.5 M=`pwd` modules
install:
    make -C /home/sice/linux-3.5 M=`pwd` INSTALL_MOD_PATH=/opt/rootfs modules_install
clean:
    make -C /home/sice/linux-3.5 M=`pwd` clean
    rm -rf led1 led2

led:
    arm-linux-gcc led1.c -o led1
    arm-linux-gcc led2.c -o led2

copy:
    cp led1 led2 led.sh /opt/rootfs

led.sh

#!/bin/bash
./led1 &
./led2 &
ps

读写自旋锁

  • 什么时候需要读写自旋锁?
    • 有时,锁的使用可以明确划分为读者和写者
  • 使用读写自旋锁的原则?
    • 多个读者能够同时持有读锁
    • 没有读者时只有一个写者能够持有写锁
  • 方法:
    初始化:



    读锁:



    写锁:

    获得读锁后再次试图获得写锁会导致死锁:

    读写自旋锁的方法如下表:


  • 在使用Linux读写自旋锁时,最后要考虑的一点是这种锁机制照顾读比照顾写要多一点
  • 当读锁被持有时,写操作为了互斥访问只能等待,但是,读者却可以继续成功地作占用锁。而自旋等待的写者在所有读者释放锁之前是无法获得锁的。
  • 所以,大量读者必定会使挂起的写者处于饥饿状态,在你自己设计锁时一定要记住这一点。
  • 实例代码如下:
    led_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <asm/atomic.h>
#include <linux/spinlock.h>
#include <linux/rwlock.h>

static unsigned long gpm4con;
#define GPM4CON (*(volatile unsigned long *)(gpm4con + 0x0))
#define GPM4DAT (*(volatile unsigned long *)(gpm4con + 0x04))


//open
int led_flag = 0;
rwlock_t lock;

ssize_t led_open(struct inode *inop,struct file *filp)
{
    GPM4CON = 0X1111;
    GPM4DAT &= ~0XF;
    printk("led open!\n");
    return 0;
}

//release
ssize_t led_release(struct inode *inop,struct file *filp)
{
    GPM4DAT |= 0xf;
    printk("led release!\n");
    return 0;
}


static unsigned char led_state = 0;
//read
ssize_t led_read(struct file *filp,char __user *buf,size_t size,loff_t f_pos)
{
    read_lock(&lock);
    copy_to_user(buf,&led_state,size);
    printk("led read data %d\n",led_state);
    read_unlock(&lock);
    return size;
}

//write
ssize_t led_write(struct file *filp,const char __user *buf,size_t size,loff_t f_pos)
{
    write_lock(&lock);
    copy_from_user(&led_state,buf,size);
    printk("led write data %d\n",led_state);
    GPM4DAT=led_state;
    write_unlock(&lock);
    return size;

}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .read = led_read,
    .write = led_write,
};


static unsigned int led_major = 0;
static struct class *led_class;
#define LED_DRV_NAME "led_drv"

static int __init led_init(void)
{
    rwlock_init(&lock);
    gpm4con=(unsigned long)ioremap(0x110002e0,12);
    led_major=register_chrdev(0,LED_DRV_NAME,&fops);
    led_class=class_create(THIS_MODULE,LED_DRV_NAME);
    device_create(led_class,NULL,MKDEV(led_major,0),NULL,LED_DRV_NAME);
    return 0;
}

static void __exit led_exit(void)
{
    device_destroy(led_class,MKDEV(led_major,0));
    class_destroy(led_class);
    iounmap((unsigned long *)gpm4con);
    unregister_chrdev(led_major,LED_DRV_NAME);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

led1.c

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

#define FILE_NAME "/dev/led_drv"

int main(void)
{
    int fd = 0;
    int i = 10; 
    
    unsigned char temp = 0;

    fd = open(FILE_NAME,O_RDWR);
    sleep(1);
    printf("led1 open ok!\r\n");    
    while(i--)
    {
        printf("led1 write:\r\n");
        usleep(3);
        write(fd,&i,sizeof(i));
        sleep(1);

        read(fd,&temp,1);
        usleep(3);
        printf("led1 read value:%d\r\n",temp);

    }

    close(fd);
    return 0;
}

led2.c

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

#define FILE_NAME "/dev/led_drv"

int main(void)
{
    int fd = 0;
    int i = 0;

    unsigned char temp = 0;
    fd = open(FILE_NAME,O_RDWR);
    sleep(1);
    printf("led2 open ok!\r\n");    
    while(i<10)
    {
        printf("led2 write:\r\n");
        usleep(5);
        write(fd,&i,sizeof(i));
        sleep(1);
        i++;

        read(fd,&temp,1);
        usleep(5);
        printf("led2 read value:%d\r\n",temp);
    }

    close(fd);
    return 0;
}

makefile和上面的代码一样,执行脚本也一样

信号量

自旋锁提供了一种快速简单的锁实现方法。如果加锁时间不长并且代码不会睡眠(比如中断处理程序),利用自旋锁是最佳选择。如果加锁时间可能很长或者代码在持有锁时有可能睡眠,那么最好使用信号量来完成加锁功能。

  • Linux中信号量是睡眠锁
    • 如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进到一个等待队列中,然后让其睡眠。这时处理器可以去执行其他代码。
    • 当持有信号量的进程将信号量释放后,处于等待队列中的那个任务将被唤醒,并获得该信号量。
  • 信号量适用于较长时间持有
  • 持有信号量时可以睡眠
  • 信号量操作头文件<asm/semaphore.h>;
  • 静态地声明信号量
DEFINE_SEMAPHORE(name)
 name是信号量的名字,初始值是1;
  • 也可以用下面方式来声明、初始化信号量
struct semaphore sem;
 sema_init(&sem,count);
 count是信号量的初值;

信号量相关的方法:

sema_init(struct semaphore *, int)
    //初始化动态创建的信号量为给定的值
down_interruptible(struct semaphore *)
    //试图获得信号量,如果不能得到则进入睡眠
down(struct semaphore *)
    //试图获得信号量,如果不能得到则进入不可中断睡眠
down_trylock(struct semaphore *)
    //试图获得信号量,如果不能得到则立即返回0
up(struct semaphore *)
    //释放给定的信号量,如果有等待的进程则唤醒
  • 同一时间,只能有一个进程打开设备文件的代码,使用信号量如下:
    led_drv.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <asm/atomic.h>
//#include <asm/semaphore.h>
#include <linux/semaphore.h>

static unsigned long gpm4con;
#define GPM4CON (*(volatile unsigned long *)(gpm4con + 0x0))
#define GPM4DAT (*(volatile unsigned long *)(gpm4con + 0x04))


//open

struct semaphore sem;

ssize_t led_open(struct inode *inop,struct file *filp)
{
    down(&sem);
    GPM4CON = 0X1111;
    GPM4DAT &= ~0XF;
    printk("led open!\n");
    return 0;
}

//release
ssize_t led_release(struct inode *inop,struct file *filp)
{
    up(&sem);
    GPM4DAT |= 0xf;
    printk("led release!\n");
    return 0;
}


static unsigned char led_state = 0;
//read
ssize_t led_read(struct file *filp,char __user *buf,size_t size,loff_t f_pos)
{
    copy_to_user(buf,&led_state,size);
    printk("led read data %d\n",led_state);
    return size;
}

//write
ssize_t led_write(struct file *filp,const char __user *buf,size_t size,loff_t f_pos)
{
    copy_from_user(&led_state,buf,size);
    printk("led write data %d\n",led_state);
    GPM4DAT=led_state;
    return size;

}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .read = led_read,
    .write = led_write,
};


static unsigned int led_major = 0;
static struct class *led_class;
#define LED_DRV_NAME "led_drv"

static int __init led_init(void)
{
    //DEFINE_SEMAPHORE(sem);
    sema_init(&sem,1);
    gpm4con=(unsigned long)ioremap(0x110002e0,12);
    led_major=register_chrdev(0,LED_DRV_NAME,&fops);
    led_class=class_create(THIS_MODULE,LED_DRV_NAME);
    device_create(led_class,NULL,MKDEV(led_major,0),NULL,LED_DRV_NAME);
    return 0;
}

static void __exit led_exit(void)
{
    device_destroy(led_class,MKDEV(led_major,0));
    class_destroy(led_class);
    iounmap((unsigned long *)gpm4con);
    unregister_chrdev(led_major,LED_DRV_NAME);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

读写信号量

  • 类似于自旋锁, 信号量也有读写信号量
  • 如果没有写者,任意多个读者可以同时持有读锁
  • 如果在代码中能够明确区分读写,则使用读写信号量
  • 创建和初始化读写信号量:
    • 静态声明读写信号量:
      DECLARE_RWSEM(name) ;
    • 初始化动态创建的读写信号量:
      init_rwsem(struct rw_semaphore *sem);
  • 操作函数
down_read(struct rw_semaphore *sem);
up_read、down_write、up_write

互斥体

  • 可以认为是“初值为1的信号量”
  • 其操作如下:
初始化:
DEFINE_MUTEX(mutexname);
mutex_init(mutex);
上锁
mutex_lock(lock);
释放
mutex_unlock(lock);

实例代码如下:
led_drv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/io.h>
#include <asm/atomic.h>
//#include <asm/semaphore.h>
#include <linux/mutex.h>

static unsigned long gpm4con;
#define GPM4CON (*(volatile unsigned long *)(gpm4con + 0x0))
#define GPM4DAT (*(volatile unsigned long *)(gpm4con + 0x04))


//open

struct mutex my_mutex;

ssize_t led_open(struct inode *inop,struct file *filp)
{
//  mutex_lock(&my_mutex);
    GPM4CON = 0X1111;
    GPM4DAT &= ~0XF;
    printk("led open!\n");
    return 0;
}

//release
ssize_t led_release(struct inode *inop,struct file *filp)
{
//  mutex_unlock(&my_mutex);
    GPM4DAT |= 0xf;
    printk("led release!\n");
    return 0;
}


static unsigned char led_state = 0;
//read
ssize_t led_read(struct file *filp,char __user *buf,size_t size,loff_t f_pos)
{
    mutex_lock(&my_mutex);
    copy_to_user(buf,&led_state,size);
    printk("led read data %d\n",led_state);
    mutex_unlock(&my_mutex);
    return size;
}

//write
ssize_t led_write(struct file *filp,const char __user *buf,size_t size,loff_t f_pos)
{
    mutex_lock(&my_mutex);
    copy_from_user(&led_state,buf,size);
    printk("led write data %d\n",led_state);
    GPM4DAT=led_state;
    mutex_unlock(&my_mutex);
    return size;

}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .read = led_read,
    .write = led_write,
};


static unsigned int led_major = 0;
static struct class *led_class;
#define LED_DRV_NAME "led_drv"

static int __init led_init(void)
{
    //DEFINE_SEMAPHORE(sem);
    mutex_init(&my_mutex);
    gpm4con=(unsigned long)ioremap(0x110002e0,12);
    led_major=register_chrdev(0,LED_DRV_NAME,&fops);
    led_class=class_create(THIS_MODULE,LED_DRV_NAME);
    device_create(led_class,NULL,MKDEV(led_major,0),NULL,LED_DRV_NAME);
    return 0;
}

static void __exit led_exit(void)
{
    device_destroy(led_class,MKDEV(led_major,0));
    class_destroy(led_class);
    iounmap((unsigned long *)gpm4con);
    unregister_chrdev(led_major,LED_DRV_NAME);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

完成变量

  • 类似于信号量
    • 一个任务在等待完成变量,另一个进程在进行某种工作
    • 另一个进程完成工作后,使用完成变量唤醒等待的进程
定义:
DECLARE_COMPLETION(work);
init_completion(struct completion *)
等待完成条件      
wait_for_completion(struct completion *)
通知等待的进程唤醒
complete(struct completion *)
complete_all(struct completion *);

大内核锁

  • 大内核锁(BKL)是全局自旋锁
    • 可以持有BKL睡眠
    • 可递归
    • 可在进程上下文中使用BKL
  • BKL多用于保护代码而不是数据
  • 在内核中不鼓励使用BKL
 使用方法:
 lock_kernel();
 /* 临界区 */
 unlock_kernel();
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容