内核中的同步

一、内核中同步的问题

假设我们把内核比作一个服务器(我是说假设),那么正在CPU上运行的进程、发出中断请求的外部设备就相当于一个客户端,客户端不断访问服务器,时间也不一定。就像是服务器会相应客户端的请求一样,内核也会响应进程与外设的请求。但同样,时间也不一定,请求的顺序也不一定。内核中有许多的共享资源,这些资源可以被不同进程所访问。这时候就会出现一个“管理”的问题。就像是大学中的班级占用教室上课一样也需要管理,如果不进行妥善管理,那么就有可能出现一个班级正在上课而另一个班级的学生也要用这个教室,这个时候就会出现冲突。而教务处就起着管理的作用,由教务处安排教室在什么时间被什么班级占用。这里的教务处就像是内核,班级就像是访问共享资源的进程,教室就像是内核中的共享资源。

二、临界区

临界区就是访问和操作共享数据的代码段。
要注意临界区指的是代码段。
我这里举个具体的例子来说:
假设有内核任务a与内核任务b,这两个任务都访问同一个共享资源变量i,
执行i++操作。
学过微机原理的各位应该知道这个i++大体上是分为三步进行的:
1)得到当前变量i的值并且拷贝到一个寄存器中。
2)将寄存器中的值加1。
3)把i的新值写回到内存中。
假设i的初值为1。当任务a、b并发执行i++操作时,期望的结果应该是一个任务完成1)2)3)之后,另一个任务再进行步骤1)2)3)。这样得出的结果应该是i=3。可实际上,有可能是这样的情况:
任务a执行1)->任务b执行1)->任务a执行2)->任务b执行2)->任务a执行3)->任务b执行3)
这样执行下来的结果是i=2。
为什么会产生这样的结果呢?因为任务a加好的结果还没有放回内存中,任务b从内存中取出的i值还是初值1。
这里的i++实际上就是一个最简单的临界区的例子。它是一个代码段,是一个访问和操作共享数据i的代码段。
任务并发的操作共享数据可能导致意想不到的结果。甚者,我们都知道内核中有各种各样的链表结构,如果并发的操作链表则可能会破坏链表的结构,结果更是可怕。所以,我们应该思考如何来保护这些对象呢?如何来管理并发任务对共享数据的访问呢?

三、内核同步措施

我这里只简要的介绍三种内核同步措施。

1 原子操作

Linux内核提供了一些专门的函数和宏,这些函数和宏作用于atomic_t类型的变量。具体的可以查阅相关资料。

2 自旋锁

自旋锁是专为防止多处理器并行而引入的一种锁。
自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被持有的自旋锁,那么这个任务就会一直进行忙循环,也就是旋转,等待锁的重新可用。如果锁没有被持有,那么请求它的内核便立即得到它并且继续执行。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区。因此这种锁可有效的避免多处理器上并行运行的内核任务竞争共享资源。具体自旋锁的使用可以查阅相关资料。

3 信号量

信号量是一种睡眠锁,也就是说,它也是一种锁机制,不同于自旋锁,当等待资源的任务没有持有锁时选择睡眠等待而不是忙循环的访问。于是,对比于自旋锁,信号量更适合用于当需等待的时间较长时。因为这样就不会降低CPU的效率来不断循环。关于信号量的使用我下面会做一个实例。

四、生产者-消费者并发实例

对于并发访问与信号量的使用我这里举一个具体的例子:
想必大家都喝过牛奶,那么,生产牛奶的厂家和经销商之间是如何进行分工合作的呢?简单来说,牛奶厂家和经销商最希望的就是厂家生产一批牛奶,经销商刚好需要的就是这么多的一批牛奶。厂家第二天再生产一批牛奶,经销商需要的还是这么多的一批牛奶。以后的日子每天如此。当然,这是理想的情况,现实不会如此。现实中厂家生产的多了,经销商不需要这么多这就是供大于求。会造成浪费。而厂家生产的少了,经销商卖完了没东西卖,这就是供不应求,钱就赚的少了,效益没有最大化。总之都不是理想情况。
这里我们将此抽象为生产者-消费者模型,厂家(producer)将牛奶放入仓库(缓冲区),经销商(consumer)将牛奶从仓库(缓冲区)中取走去售卖。
话不多说,代码如下:

#include<linux/kthread.h>
#include<linux/init.h>
#include<linux/module.h>
#include<linux/semaphore.h>
#include<linux/sched.h>
#include<asm/atomic.h>
#include<linux/delay.h>
#define PRODUCT_NUMS 10

static struct semaphore sem_producer;
static struct semaphore sem_consumer;

static char product[12];
static atomic_t num;
static int producer(void *product);
static int consumer(void *product);
static int id = 1;
static int consume_num = 1;

static int producer(void *p)
{
  char *product = (char *)p;
  int i;

  atomic_inc(&num);
  printk("producer[%d] start...\n",current->pid);
  for(i = 0;i<PRODUCT_NUMS;i++){
    down(&sem_producer);
    snprintf(product,12,"2017-04-%d",id++);
    printk("producer[%d]produce %s\n",current->pid,product);
    up(&sem_consumer);
    if(id == 10)
      msleep(400);
  }
      msleep(400);
  printk("producer[%d] exit...\n",current->pid);
  return 0;
}

static int consumer(void *p)
{
  char *product = (char *)p;

  printk("consumer[%d] start...\n",current->pid);
  for(;;){
    down_interruptible(&sem_consumer);
    if(consume_num >= PRODUCT_NUMS * atomic_read(&num))
      break;
    printk("consumer[%d] consume %s\n",current->pid,product);
    consume_num++;
    memset(product,'\0',12);
    up(&sem_producer);
  }
  printk("consumer[%d] consume %s\n",current->pid,product);
  printk("consumer[%d] exit...\n",current->pid);
  return 0;
}

static int procon_init(void)
{
  printk(KERN_INFO"show producer and consumer\n");
  sema_init(&sem_producer,1);
  sema_init(&sem_consumer,0);
  atomic_set(&num,0);
  kthread_run(producer,product,"conpro_1");
  kthread_run(consumer,product,"compro_1");
  return 0;
}

static void procon_exit(void)
{
  printk(KERN_INFO"exit producer and consumer\n");
}

module_init(procon_init);
module_exit(procon_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("producer and consumer Module");
MODULE_ALIAS("a simplest module");

这是一个内核模块,所以需要用Makefile文件进行编译模块。我这里把Makefile文件内容也写在这里:
obj-m := procon.o
CURRENT_PATH := (shellpwd)LINUXPATH:= (shell uname -r)
LINUX_KERNEL_PATH := /usr/src/kernels/$(LINUX_PATH)

all:
make -C (LINUXKERNELPATH)M= (CURRENT_PATH) modules
clean:
make -C (LINUXKERNELPATH)M= (CURRENT_PATH) clean

写好这两个程序后,使用make编译模块,
insmod procon.ko加载模块,然后用dmesg来观察结果。
我的结果是这样的:
这里写图片描述
噢,忘了说了,我的内核版本是3.10.0的。版本不同可能某些内核函数是不同的。但如果版本比我高的内核基本上是没问题的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值