全部博文(183)
分类: 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》测试并设置:
代码大概长的这样的: