关闭

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

513人阅读 评论(0) 收藏 举报

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

其实我自己也没有完全掌握, 写完后, 又调试了一下, 真的是学问不小 , 学无止境啊,
大家都讨论讨论, 看看有什么需要更加完善的, 或者从培训新人角度, 加上一些更好的练习。
毕竟从学习的角度,弄个好例子真的很不容易。
让新人从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

0
0

猜你在找
【直播】机器学习&数据挖掘7周实训--韦玮
【套餐】系统集成项目管理工程师顺利通关--徐朋
【直播】3小时掌握Docker最佳实战-徐西宁
【套餐】机器学习系列套餐(算法+实战)--唐宇迪
【直播】计算机视觉原理及实战--屈教授
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之矩阵--黄博士
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之凸优化--马博士
【套餐】Javascript 设计模式实战--曾亮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:834次
    • 积分:18
    • 等级:
    • 排名:千里之外
    • 原创:1篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章存档