Chinaunix首页 | 论坛 | 博客
  • 博客访问: 715972
  • 博文数量: 183
  • 博客积分: 2650
  • 博客等级: 少校
  • 技术积分: 1428
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-22 17:02
文章分类
文章存档

2017年(1)

2015年(46)

2014年(4)

2013年(8)

2012年(2)

2011年(27)

2010年(35)

2009年(60)

分类: LINUX

2009-11-29 21:33:30

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

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

#include

#include

#include /* for put_user()/get_user() */

#include

#include

#include /* struct list_head */

#include /* 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;

}

* 要加保护,防止静态

*/

我的总结, 具体的权威的介绍, 可以参阅《linux内核设计与分析》里面的内核同步方法介绍:

到底什么时候需要加锁?

从编程的角度:

1》测试并设置:

代码大概长的这样的:


if(!a)
{ a++;
xxxxx;
}
else
return ;

这种形式,肯定要加锁,否则就会发生竟态条件
其实最好的办法就是用原子变量和原子bit最好。
类似:

if( ! atomic_inc_and_test(&inuse_atomic)) //原子加1 , 并判断是否==0 , -1
, 表示无人使用此设备
{
dbg("%d process is opening this device \n",inuse_atomic);//如果值是1
, 说明刚好有一个进程在访问该设备
dbg("open device failure \n");
return -EBUSY;
}
//try_module_get(THIS_MODULE);//2.6 kernel
try_module_get(THIS_MODULE);
return SUCCESS;

或者:

if(test_and_set_bit(0,&chardev_users))
return -EBUSY;
xxxxx();








2> 一个变量在一段代码的不同的地方,
既有读/写,或者写/写,这个时候一定要加锁保护,防止竞争条件发生。

比如上面的例子:里面sleepers,上面的部分就有写(递减) , 下面就读(判断是否 > 0
,如果 > 0 ,就唤醒队列)

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);


......

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));




3> 一个共享数据, 在不同的函数中被操作,最简单的例子就是中断,
在中断处理函数中可能会修改某个flag , 而在主程序中会判断这个flag ,因此必须上锁:
举个例子:
RTC驱动:

void rtc_update(unsigned long num, unsigned long events)
{
spin_lock(&rtc_lock);
rtc_irq_data = (rtc_irq_data + (num << 8)) | events;
spin_unlock(&rtc_lock);

wake_up_interruptible(&rtc_wait);
kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
}



...

static ssize_t
rtc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
DECLARE_WAITQUEUE(wait, current);
unsigned long data;
ssize_t ret;

if (count < sizeof(unsigned long))
return -EINVAL;

add_wait_queue(&rtc_wait, &wait);
do {
__set_current_state(TASK_INTERRUPTIBLE);

spin_lock_irq(&rtc_lock);
data = rtc_irq_data;
rtc_irq_data = 0;
spin_unlock_irq(&rtc_lock);

if (data != 0) {
ret = 0;
break;
}
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
break;
}
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
schedule();
} while (1);
set_current_state(TASK_RUNNING);
remove_wait_queue(&rtc_wait, &wait);



4> 编程者想保护一段代码不能被抢占,
这样的话, 可以加锁防止抢占和强制调度。典型的就是preempt_disable() ,
spin_lock() 就是调用了 preempt_disable()宏的。

典型的例子就是上面:

这里的意图很简单, 资源小于<0 马上schedule(),不能被打断
资源>0 , 就break , 不能被打断。


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));


另外注意一点:
原子操作是不可被打断的, 但是两个原子操作中间却是可以被打断的,
如果你想这两个原子操作不能被打断, 就可以用上锁保护起来。

要明白这一点, 虽然我上面的例子, 里面的资源个数和睡眠进程个数都是原子变量,
但是仅仅是确保了他们递增或者递减是原子的, 却不能保证临界区是原子的 。
所以要加锁保护起来。



阅读(2643) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~