写给新人的练习互斥和避免竟态条件的例子(简单的信号量实现)

原创 2007年10月05日 17:24:00

今天第一天上班, 就俩人, 挺无聊的, 就写了一个例子,
让新人练习信号量,自旋锁,原子操作的函数。

其实我自己也没有完全掌握, 写完后, 又调试了一下, 真的是学问不小 , 学无止境啊,
大家都讨论讨论, 看看有什么需要更加完善的, 或者从培训新人角度, 加上一些更好的练习。
毕竟从学习的角度,弄个好例子真的很不容易。
让新人从kernel代码里面去扒代码确实有点为难他们了。

这里的格式没有颜色,如果想有颜色,访问这里: href=http://blog.chinaunix.net/u/22617/showart.php?id=394723>http://blog.chinaunix.net
/u/22617/showart.php?id=394723


代码可以到这里下载: href=http://blog.chinaunix.net/upfile/071004163113.rar>http://blog.chinaunix.net/upfil
e/071004163113.rar

含有一个c,一个.h 和一个Makefile(根据需要改一下路径即可)


kernel:2.6.18.8
代码如下:


/*
* Author:bob_zhang2004@163.com
* 本例子是个练习互斥同步的例子,你可以自由copy,随意修改
* 如果你要扩充或者修改,而且自己觉得很好,可以发给我到上面的mail
*/

/*
* 练习的小项目:
* 实际上是实现了一个类似信号量的一个东西:系统有n个资源(用struct
bob_resource来定义),当用户read的时候,先判断资源个数 > 0
* 如果>0,就继续执行,如果<0 就进入等待队列,并用sleepers来记录睡眠的进程个数。
* 当获得资源的进程释放的资源以后, 就必须left_res_nr++ ,然后唤醒等待队列中的进程,
当某个进程被调度后, sleepers就会递减。
*/

/*
* 学习重点:
*
掌握住信号量,自旋锁,原子操作,等待队列的用法,包括如何声明,如何初始化,特别自旋锁和
信号量的初始化方法
* 熟练 常用的函数的用法
*/


/* 练习中关于互斥和避免静态条件注意的问题:
* 为什么我要在里面加上自旋锁的保护,不加可不可以呢?
* 里面我加的自旋锁的保护效率是否高呢?临界区会不会太长?
* 仔细想想,如果不加自旋锁的保护,大概会出现什么样的静态条件呢?
*/

/*
* 下一步的练习
* 更加深入的学习, 用保护的时机
*/

/* 最后看我的总结,在程序的最后面 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h> /* for put_user()/get_user() */
#include <linux/init.h>
#include <linux/compiler.h>
#include <linux/list.h> /* struct list_head */
#include <linux/delay.h> /* mdelay() */

#define KERNEL_DEBUG //defined debug function

#include "mod_head.h"

//bob自己的资源结构体,其实有点类似于semaphore的定义
//在这里我们自己练习一下semaphore是怎么样实现的,当然我们都是用c实现的
//实际工作中,可能要用汇编语言提高效率
typedef struct bob_resource
{
spinlock_t
spin_lock; //保护该资源结构的自旋锁,用在SMP处理机上,对于单处理机上,可以防止抢占
char res_name[20]; //资源的名称
atomic_t left_res_nr; //剩余的资源个数
atomic_t sleepers; //因为得不到资源而睡眠的进程个数
wait_queue_head_t res_wait_queue; //睡眠进程所在的等待队列
}resource_t;

resource_t tt_res;

static int pid_n = 0;

static __init int chardev_init(void);
static __exit void chardev_exit(void);

static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
//static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

#define SUCCESS 0
#define DEVICE_NAME "chardev_bob_test" /* Dev name as it appears in /proc/devices
*/

static int Major;

static int device_open(struct inode *inode, struct file *file)
{
//MOD_INC_USE_COUNT;
try_module_get(THIS_MODULE);

return SUCCESS;
}


static int device_release(struct inode *inode, struct file *file)
{

//MOD_DEC_USE_COUNT;
module_put(THIS_MODULE);

return 0;
}
/*
void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;

wait->flags &= ~WQ_FLAG_EXCLUSIVE;
spin_lock_irqsave(&q->lock, flags);
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock, flags);
}
*/
/* 由此可以看出, add_wait_queue() 是安全的,并不会发生静态条件 */

/* 关于原子操作,参阅《linux内核设计与实现》的106页
* 关于自旋锁的操作,参阅《linux内核设计与实现》的109页
* 关于等待队列,google,或者参阅edwin的粘贴的一篇文章
*/

/* 从这里我们还可以进一步思考:
* typedef struct bob_resource
{
spinlock_t
spin_lock; //保护该资源结构的自旋锁,用在SMP处理机上,对于单处理机上,可以防止抢占
char res_name[20]; //资源的名称
atomic_t left_res_nr; //剩余的资源个数
atomic_t sleepers; //因为得不到资源而睡眠的进程个数
wait_queue_head_t res_wait_queue; //睡眠进程所在的等待队列
}resource_t;
* 定义的是否合理?为什么要有sleepers字段呢?假使光有一个left_res_nr 行不行呢?
* 我最开始的做法,就是光加一个left_res_nr ,为正表示有资源,为负,表示睡眠的进程个数
* 但是具体编程的时候, 我发现非常的不方便,而且对于处理睡眠再唤醒的时候,无法处理
* 所以我终于明白了,为什么struct semaphore为什么要有一个sleepers字段了
*/
static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */
char *buffer, /* buffer to fill with data */
size_t length, /* length of the buffer */
loff_t *offset) /* loff_t == long long */
{

int ret = 0;
int i = 0;
DECLARE_WAITQUEUE(wait, current);

add_wait_queue(&(tt_res.res_wait_queue), &wait);
do {
__set_current_state(TASK_INTERRUPTIBLE);
dbg("tt_res.left_res_nr.counter = %d/n",tt_res.left_res_nr.counter);

spin_lock(&(tt_res.spin_lock)); //防止抢占(UP) ,加锁保护(SMP)
if(atomic_read(&(tt_res.left_res_nr)) > 0)
{
if(atomic_read(&(tt_res.sleepers)) > 0)
atomic_dec(&(tt_res.sleepers));
ret = 0;
break;
}
atomic_inc(&(tt_res.sleepers));
spin_unlock(&(tt_res.spin_lock));
schedule();
} while (1);

atomic_dec(&(tt_res.left_res_nr));
spin_unlock(&(tt_res.spin_lock));

set_current_state(TASK_RUNNING);
remove_wait_queue(&(tt_res.res_wait_queue), &wait);


/* 这里实际上就是你自己要执行的代码 ,我这里仅仅是个例子,睡眠了10秒钟而已
* ==============================================================================
*/
dbg("I am %dth process /n",++pid_n);
//do something
dbg("I begin to do somthing baseon the resource /n");
dbg("in fact ,I will sleep 10s/n");

//sleep 10s
while(i < 100)
{
mdelay(100);
i++;
}

/*==============================================================================*/

//思考:为什么,我会注释掉这里的自旋锁的保护呢?如果这个时候发生了进程切换又会怎么样呢

//因为:没有必要对原子操作加锁,即使发生了进程切换,也不会打断原子操作的
//作业:自己仔细分析一下,可能会发生的多个进程切换的过程和情景分析
dbg("current ,tt_res.left_res_nr.counter =
%d/n",atomic_read(&(tt_res.left_res_nr)));
// spin_lock(&(tt_res.spin_lock));
atomic_inc(&(tt_res.left_res_nr));
// spin_unlock(&(tt_res.spin_lock));

dbg("I have free resource /ntt_res.left_res_nr.counter =
%d/n",atomic_read(&(tt_res.left_res_nr)));
//唤醒等待队列里面的进程

//保护tt_res.sleepers ,因为上面的 atomic_dec(&(tt_res.sleepers));
在别的进程中也会修改 sleepers 可能会引发竟态条件
spin_lock(&(tt_res.spin_lock));
if(atomic_read(&(tt_res.sleepers)) > 0) {
dbg("sleepers = %d/n",atomic_read(&(tt_res.sleepers)));
wake_up_interruptible(&(tt_res.res_wait_queue));
//唤醒等待队列里面的所有进程,但是最终也只有一个进程能获得自愿而已
}
spin_unlock(&(tt_res.spin_lock));
return ret;
}

static struct file_operations fops = {
.owner = THIS_MODULE,
.read = device_read,
.open = device_open,
.release= device_release
//others are NULL;
};


static __init int chardev_init(void)
{

Major = register_chrdev(0, DEVICE_NAME, &fops);
if (Major < 0) {
dbg("Registering the character device failed with %d/n", Major);
return Major;
}

//resource excerise,resource initializing
//init_MUTEX(&(tt_res.sema_lock)); //互斥信号量
spin_lock_init(&(tt_res.spin_lock));
strcpy(tt_res.res_name,"bob_resource");
//默认的资源个数只有 1 个, 便于调试方便,你可以自己更改更多的,变成类似多元信号量
atomic_set(&(tt_res.left_res_nr),1);
atomic_set(&(tt_res.sleepers),0);
init_waitqueue_head(&(tt_res.res_wait_queue));

return 0;
}

static __exit void chardev_exit(void)
{
/*
* Unregister the device
*/
int ret = unregister_chrdev(Major, DEVICE_NAME);

struct list_head *p = NULL;

if (ret < 0)
dbg("Error in unregister_chrdev: %d/n", ret);
}

module_init(chardev_init);
module_exit(chardev_exit);

MODULE_AUTHOR("Bob Zhang");
MODULE_DESCRIPTION("Excerise for spinlock,mutex,atomic etc.");
MODULE_LICENSE("GPL");

/* 总结:
* 1。什么时候要加保护,一个数据结构,在一段代码里面,既有读 ,又有写,
显然要加保护,因为进程是并发的,
* 2。可能一个进程正在写, 另外一个进程正在读,为防止静态条件,
要加保护,可以是信号量,也可以是spinlock
* 对于结构 if(a) {
xxx;
}
* 要加保护,防止静态
*/




--------------------
http://KernelChina.cublog.cn

Race condition【竟态条件】(java并发编程实战第二章)

多线程环境下,无需调用方进行任何同步处理也能保证正确性的类是线程安全的类   无状态的对象是线程安全的。无状态是指没有成员变量。由于方法的局部变量都是在线程私有的栈中分配的,因此在一个线程...
  • u012572955
  • u012572955
  • 2017年02月09日 15:36
  • 483

并发和竟态

并发和竞态 并发:多个执行单元同时,并行的被执行。 竞态:并发的执行单元对共享资源(硬件资源和软件上的全局变量,静态变量等)的访问导致发生竞态。 竟态的几种情况: ...
  • qzh1996
  • qzh1996
  • 2016年11月18日 14:37
  • 437

线程同步之竟态条件

 如果两个或多个线程访问相同的对象,或者访问不同步的共享状态,就会出现竟态条件。 在单核的CPU中,若线程调度是抢先式的,就会出现此问题;在多CPU或双核CPU系统中,其多个线程可以同时运行,竟态条件...
  • zhiguo2010
  • zhiguo2010
  • 2010年07月07日 10:14
  • 281

linux设备驱动第五篇:驱动中的并发与竟态

在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争。 首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时、并行被执行。...
  • HAOMCU
  • HAOMCU
  • 2015年04月11日 13:23
  • 2471

多线程1:竞态条件

竞态条件(Race Condition):计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。 最常见的竞态条件为: 一,先检测后执行。执行依赖于检测的结果,而检测结果依赖于多个线程的执...
  • a19881029
  • a19881029
  • 2012年11月20日 10:19
  • 7282

进程间通信机制(管道、信号、共享内存/信号量/消息队列)、线程间通信机制(互斥锁、条件变量、posix匿名信号量)

一、管道 在Linux 中,管道是一种使用非常频繁的通信机制。从本质上说,管道也是一种文件,但它又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题,具体表现如下所述。 • 限制管道...
  • Simba888888
  • Simba888888
  • 2013年09月16日 19:43
  • 5543

使用信号量实现线程互斥

在上一篇文章中,讲述了线程中互斥锁的使用,达到对共享资源互斥使用。除了使用互斥锁,信号量,也就是操作系统中所提到的PV原语,能达到互斥和同步的效果,这就是今天我们所要讲述的信号量线程控制。 PV...
  • yhcs1213
  • yhcs1213
  • 2015年09月08日 16:49
  • 1228

用信号量解决进程的同步与互斥

现代操作系统采用多道程序设计机制,多个进程可以并发执行,CPU在进程之间来回切换,共享某些资源,提高了资源的利用率,但这也使得处理并发执行的多个进程之间的冲突和相互制约关系成为了一道难题。如果对并发进...
  • guoweimelon
  • guoweimelon
  • 2016年03月10日 19:42
  • 1997

并发编程五:竞态条件与临界区

并发编程:竞态条件与临界区介绍当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竟态条件发生的代码称作临界区。备注:以下这段代码就存在竞态条件,其中return ++count...
  • nicewuranran
  • nicewuranran
  • 2016年08月14日 14:59
  • 458

一个简单的Java信号量例子

 一个简单的Java信号量例子2010-12-20 星期一 晴朗最近搞个CRM搜索排序服务工具,需要在对searchweb的调用接口作并发量控制(每天机器最多允许20个并发)。信号量最能作这种事情了。...
  • arganzheng
  • arganzheng
  • 2011年03月26日 21:35
  • 4318
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:写给新人的练习互斥和避免竟态条件的例子(简单的信号量实现)
举报原因:
原因补充:

(最多只允许输入30个字)