全部博文(298)
分类: LINUX
2011-03-24 12:48:09
(5)信号量及其缓冲区应用
注:所以文章红色字体代表需要特别注意和有问题还未解决的地方,蓝色字体表示需要注意的地方
1.本文所介绍的程序平台
开发板:arm9-mini2440
虚拟机为:Red Hat Enterprise Linux 5
开发板上系统内核版本:linux-2.6.32.2
并发与竞态
并发:多个执行单元同时被执行。
竞态:并发的执行单元对共享资源(硬件资源和软件上的全局变量等的访问导致的竞争状态
例:
if (copy_from_user(&(dev->data[pos]), buf, count))
ret = -EFAULT;
goto out;
假设有 2个进程试图同时向一个设备的相同位置写入数据,就会造成数据混乱。
处理并发的常用技术是加锁或者互斥,即确保在任何时间只有一个执行单元可以操作共享资源。在Linux内核中主要通过semaphore机制和spin_lock机制实现。
信号量
Linux内核的信号量在概念和原理上与用户态的信号量是一样的,但是它不能在内核之外使用,它是一种睡眠锁。如果有一个任务想要获得已经被占用的信号量时,信号量会将这个进程放入一个等待队列,然后让其睡眠。当持有信号量的进程将信号释放后,处于等待队列中的任务将被唤醒,并让其获得信号量。
信号量在创建时需要设置一个初始值,表示允许有几个任务同时访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果释放后信号量的值为非正数,表明有任务等待当前信号量,因此要唤醒等待该信号量的任务。
信号量的实现也是与体系结构相关的,定义在
1. 定义信号量
struct semaphore sem;
2. 初始化信号量
void sema_init (struct semaphore *sem, int val)
该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。
void init_MUTEX (struct semaphore *sem)
该函数用于初始化一个互斥锁,即它把信号量 sem的值设置为1。
void init_MUTEX_LOCKED (struct semaphore *sem)
该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。
定义与初始化的工作可由如下宏一步完成:
DECLARE_MUTEX(name)
定义一个信号量name,并初始化它的值为1。
DECLARE_MUTEX_LOCKED(name)
定义一个信号量name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。
3. 获取信号量
void down(struct semaphore * sem)
获取信号量sem,可能会导致进程睡眠,因此不能在中断上下文使用该函数。该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到 的任务释放该信号量才能继续运行。
int down_interruptible(struct semaphore * sem)
获取信号量sem。如果信号量不可用,进程将被置为TASK_INTERRUPTIBLE类型的睡眠状态。该函数由返回值来区分是正常返回还是被信号中断返回,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。
down_killable(struct semaphore *sem)
获取信号量sem。如果信号量不可用,进程将被置为TASK_KILLABLE类型的睡眠状态。
注:
down()函数现已不建议继续使用。建议使用
down_killable() 或 down_interruptible() 函数。
4. 释放信号量
void up(struct semaphore * sem)
该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待者。
自旋锁
自旋锁最多只能被一个可执行单元持有。自旋锁不会引起调用者睡眠,如果一个执行线程试图获得一个已经被持有的自旋锁,那么线程就会一直进行忙循环,一直等待下去,在这里看是否该自旋锁的保持者已经释放了锁,"自旋"就是这个意思。
spin_lock_init(x)
该宏用于初始化自旋锁x,自旋锁在使用前必须先初始化。
spin_lock(lock)
获取自旋锁lock,如果成功,立即获得锁,并马上返回,否则它将一直自旋在这里,直到该自旋锁的保持者释放。
spin_trylock(lock)
试图获取自旋锁lock,如果能立即获得锁,并返回真,否则立即返回假。它不会一直等待被释放。
spin_unlock(lock)
释放自旋锁lock,它与spin_trylock或spin_lock配对使用。
信号量PK自旋锁
信号量可能允许有多个持有者,而自旋锁在任何时候只能允许一个持有者。当然也有信号量叫互斥信号量(只能一个持有者),允许有多个持有者的信号量叫计数信号量。信号量适合于保持时间较长的情况;而自旋锁适合于保持时间非常短的情况,在实际应用中自旋锁控制的代码只有几行,而持有自旋锁的时间也一般不会超过两次上下文切换的时间,因为线程一旦要进行切换,就至少花费切出切入两次,自旋锁的占用时间如果远远长于两次上下文切换,我们就应该选择信号量。
2.程序清单
本次实验程序为tekkamanninja博客《LDD3》代码,本人作了改动和较为详细的注释,如有错误请指出。
这次程序会用到一个宏container_of(ptr, type, member),下面的第一个程序为测试该宏左右的程序。
container_of.c
#include
// container_of(ptr, type, member)原型
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)
#define container_of(ptr, type, member)({ \
const typeof( ((type *)0)->member ) *__mptr =(ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
struct gong_buf
{
char name[20];
int age;
double weight;
}test={"gongzhi", 12, 50};
int main()
{
struct gong_buf *ptr;
/* container_of(ptr, type, member)
member为type结构的成员,ptr为member成员的指针*/
ptr = container_of(&test.age, struct gong_buf, age);
printf("%s %d \n", ptr->name, ptr->age);
return 0;
}
虚拟机测试:
gong_spinlock.h
#ifndef _GONG_H_
#define _GONG_H_
#include
#ifndef GONG_MAJOR
#define GONG_MAJOR 0 /*预设的mem的主设备号*/
#endif
#ifndef GONG_NR_DEVS
#define GONG_NR_DEVS 3/*设备数*/
#endif
#ifndef GONG_SIZE
#define GONG_SIZE 4096
#endif
#define BUFSIZE 64
struct gong_buf
{
/*缓冲区*/
// unsigned int access_key; /* used by sculluid and scullpriv */
struct semaphore sem; /* mutual exclusion semaphore */
spinlock_t lock;
struct cdev cdev; /* Char device structure */
struct kfifo *gongkfifo;
wait_queue_head_t inq, outq; /* read and write queues */
};
/*定义幻数*/
#define GONG_IOC_MAGIC 'g'
/* 定义命令 ,这里的命令都是unsigned int类型*/
#define GONG_IOCPRINT _IO(GONG_IOC_MAGIC, 1) /*没有参数的命令*/
#define GONG_IOCGETDATA _IOR(GONG_IOC_MAGIC, 2, int) /*从驱动中读数据*/
#define GONG_IOCSETDATA _IOW(GONG_IOC_MAGIC, 3, int) /*写数据到驱动中*/
//定义命令的最大序列号
#define GONG_IOC_MAXNR 3
/*
* Ioctl definitions
*/
/* Use 'k' as magic number */
#define GONG_KFIFO_MAGIC 'k'
/* Please use a different 8-bit number in your code */
#define GONG_KFIFO_SIZE _IO(GONG_KFIFO_MAGIC, 0)
#define GONG_KFIFO_RESET _IO(GONG_KFIFO_MAGIC, 1)
/* ... more to come */
#define GONG_KFIFO_MAXNR 1
#endif
gong_spinlock.c
/*********************************************
* 阻塞版本
*********************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "gong_spinlock.h"
static int gong_major = GONG_MAJOR;
module_param(gong_major, int, S_IRUGO);
//缓冲区
struct gong_buf *gong_buf;/*缓冲区结构指针*/
unsigned char *gongbuffer;
unsigned char *gong;
//struct kfifo {
// unsigned char *buffer; /* 使用的缓冲区头指针 */
// unsigned int size; /* 缓冲区总大小 */
// unsigned int in; /* 已写入缓冲区的数据总量,当前缓冲区写指针的偏移量:(in % size) */
// unsigned int out; /* 已读出缓冲区的数据总量,当前缓冲区读指针的偏移量:(out % size) */
// spinlock_t *lock; /* 为避免竞态的自旋锁 */
//};/*当in==out时,缓冲区为空;当(in-out)==size时,缓冲区已满*/
/*
struct file 常见成员:
loff_t f_pos 文件读写位置
struct file_operation *f_op
struct inode 常见成员:
dev_t i_rdev 设备号
*/
/*
为啥不能放在这里
static const struct file_operations gong_fops =
{
.owner = THIS_MODULE,
.llseek = gong_llseek,
.read = gong_read,
.write = gong_write,
.open = gong_open,
.release = gong_release,
.ioctl = gong_ioctl,//!!为什么这里有‘,’??
};//!!!
*/
/*文件打开函数*/
int gong_open(struct inode *inode, struct file *filp)
{
struct gong_buf *dev;
/*获取次设备号*/
int num = MINOR(inode->i_rdev);
/*大于最大的次设备号就错误了 因为注册的时候要指定次设备的数目*/
if(num >= GONG_NR_DEVS)
return -ENODEV;
//dev_t 结构 inode->i_rdev设备号
//struct cdev * 结构 inode->i_cdev
/*
struct gong_buf
{
struct semaphore sem;
spinlock_t lock;
struct cdev cdev;
struct kfifo *gongkfifo;
wait_queue_head_t inq, outq;
};
container_of(ptr, type, member)
member为type结构的成员,ptr为member成员的指针
*/
dev = container_of(inode->i_cdev, struct gong_buf, cdev);//
filp->private_data = dev;
return nonseekable_open(inode, filp); /* success */
return 0;
}
/*文件释放函数*/
int gong_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*读函数*/
static ssize_t gong_read(struct file *filp, char __user *buf, size_t count,//注意这里的count
loff_t *poss)
{
ssize_t retval = 0;
/*获得设备结构体指针由于read没有struct inode *inode结构 而且函数的
参数不能改 只能改函数名所以只能在open函数里面设置*/
struct gong_buf *dev = filp->private_data;
//int down_interruptible(struct semaphore *sem);
/*推荐使用,使用down_interruptible需要格外小心,若操作被中断,
该函数会返回非零值,而调用这不会拥有该信号量。对down_interruptible
的正确使用需要始终检查返回值,并做出相应的响应。*/
/*对于Linux,上层库函数会根据系统调用的ERESTARTSYS返回值重启该系统调用,
而对于Solaris则会让系统调用失败,在Linux中,重启的系统调用会再次检查缓冲区,
为空,说明刚才的信号不是缓冲区有数据了的信号,继续等待,重复刚才的过程,不为空,
就可以直接处理数据,系统调用正常结束
*/
if (down_interruptible(&(dev->sem)))
return -ERESTARTSYS;
//unsigned int kfifo_len(struct kfifo *fifo);
/*fifo:要操作的缓冲区结构体指针;
函数返回缓冲区实际已有的数据字节数,内部实现十分简单,就是in - out;
返回值为缓冲区已有的数据字节数。
*/
while (!kfifo_len(dev->gongkfifo)) { /* nothing to read */
//void up(struct semaphore *sem);
/*任何拿到信号量的线程都必须通过一次(只有一次)
对up的调用而释放该信号量。在出错时,要特别小心;若在拥有一个信号量时发生错误,
必须在将错误状态返回前释放信号量。*/
up(&dev->sem); /* release the lock */
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
printk("\"%s\" reading: going to sleep\n", current->comm);//??
if (wait_event_interruptible(dev->inq, kfifo_len(dev->gongkfifo)))
return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
/* otherwise loop, but first reacquire the lock */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
if (count > kfifo_len(dev->gongkfifo))
count = kfifo_len(dev->gongkfifo);//
count = kfifo_get(dev->gongkfifo,gong, count);
if (copy_to_user(buf, gong, count)) {
retval = -EFAULT;
goto out;
}
retval = count;
out:
up(&dev->sem);
wake_up_interruptible(&dev->outq);
if (printk_ratelimit()) printk("\"%s\" did read %li bytes\n",current->comm, (long)retval);
return retval;
}
ssize_t gong_write(struct file *filp, const char __user *buf, size_t count,//注意这里的count
loff_t *f_pos)
{
struct gong_buf *dev = filp->private_data;
ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
if ( count>BUFSIZE ) count = BUFSIZE;
if (copy_from_user(gong, buf, count)) {
up (&dev->sem);
return -EFAULT;
}
count = kfifo_put(dev->gongkfifo,gong, count);
retval = count;
up(&dev->sem);
wake_up_interruptible(&dev->inq); /* blocked in read() and select() */
if (printk_ratelimit()) printk("\"%s\" did write %li bytes\n",current->comm, (long)count);
return retval;
}
int gong_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
int gong = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != GONG_KFIFO_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > GONG_KFIFO_MAXNR) return -ENOTTY;
switch(cmd) {
/*
* The following two change the buffer size for scullpipe.
* The scullpipe device uses this same ioctl method, just to
* write less code. Actually, it's the same driver, isn't it?
*/
case GONG_KFIFO_SIZE:
gong = (int) kfifo_len(gong_buf->gongkfifo);
break;
case GONG_KFIFO_RESET:
kfifo_reset(gong_buf->gongkfifo);
break;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return gong;
}
static const struct file_operations gong_kfifo_fops =
{
.owner = THIS_MODULE,
//.llseek = gong_llseek,
.read = gong_read,
.write = gong_write,
.open = gong_open,
.release = gong_release,
.llseek = no_llseek,
.ioctl = gong_ioctl,//!!为什么这里有‘,’??
};//!!!
static void gong_kfifo_setup_cdev(struct gong_buf *dev)
{
int err, devno = MKDEV(gong_major, 0 );
/*设备注册:
1.分配cdev
动态分配:
struct cdev *cdev_alloc(void)
2.初始化cdev
void cdev_init(struct cdev *cdev, const struct file_operation *fops)
3.添加cdev
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
*/
cdev_init(&dev->cdev, &gong_kfifo_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add (&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding gong_buf", err);
}
/*设备驱动模块加载函数*/
static int gong_init(void)
{
int res;
/*dev_t描述是设备 MKDEV把主设备号和次设备号合成*/
dev_t devno = MKDEV(gong_major, 0);
/*静态申请设备号*/
/*int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:注册从from开始的count个这备号,主设备号不变,次设备号增加,如果次设备号溢出,主设备号加1
参数:
from:要注册的第一个设备号
count:要注册的设备号个数
name:设备名(体现在/proc/devices)
*/
if(gong_major)
res = register_chrdev_region(devno, GONG_NR_DEVS, "gong_buf");
else/*动态申请设备号*/
/*int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
功能:动态申请count个设备号,第一个设备号的次设备号为baseminor
参数:
dev:分配到得设备号
baseminor:起始次设备号
count:要注册的设备号个数
name:设备名(体现在/proc/devices)
*/
{
res = alloc_chrdev_region(&devno, 0, GONG_NR_DEVS, "gong_buf");
gong_major = MAJOR(devno);
}
printk(KERN_NOTICE "1\n");
if(res < 0)
{
printk(KERN_WARNING "gong: can't get major %d\n", gong_major);
return res;
}
//必须要先把gong_buf分配才可以分配以后的
gong_buf = kmalloc( sizeof(struct gong_buf), GFP_KERNEL);
if (!gong_buf) {
res = -ENOMEM;
goto fail_malloc; /* Make this more graceful */
}
memset(gong_buf, 0, sizeof(struct gong_buf));
printk(KERN_NOTICE "2\n");
//分配缓冲区空间
gongbuffer = kmalloc(BUFSIZE, GFP_KERNEL);
if (!gongbuffer) {
res = -ENOMEM;
goto fail_malloc; /* Make this more graceful */
}
gong = kmalloc(BUFSIZE, GFP_KERNEL);
if (!gong) {
res = -ENOMEM;
goto fail_malloc; /* Make this more graceful */
}
printk(KERN_NOTICE "3\n");
/* Initialize each device. 初始化话各种锁机制 */
init_MUTEX(&gong_buf->sem);
spin_lock_init (&gong_buf->lock);
/**************************************
struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size,gfp_t gfp_mask, spinlock_t *lock);
调用kfifo_init必须保证size是2的整数次幂,
而且buffer只接受一个已分配好空间的指针。
也就是说之前要使用kmalloc分配好空间,
将返回的指针传递到buffer
****************************************/
gong_buf->gongkfifo = kfifo_init(gongbuffer, BUFSIZE ,GFP_KERNEL, &gong_buf->lock);
init_waitqueue_head(&gong_buf->inq);
init_waitqueue_head(&gong_buf->outq);
gong_kfifo_setup_cdev(gong_buf);//??!!
printk(KERN_NOTICE "4\n");
return 0;
fail_malloc:
/*void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放从from开始的count个设备号*/
unregister_chrdev_region(devno, GONG_NR_DEVS);
return res;
}
static void gong_exit(void)
{
int keke = 0xff;
/* Get rid of our char dev entries */
if (gong_buf) {
if (gong_buf->gongkfifo) { kfifo_free(gong_buf->gongkfifo);
//kfree(gong_buf->gongkfifo);
keke = 0;
}
cdev_del(&gong_buf->cdev);
kfree(gong_buf);
}
#if 1
if (gongbuffer && keke) kfree(gongbuffer);
#endif
if (gong) kfree(gong);
/* cleanup_module is never called if registering failed */
unregister_chrdev_region(MKDEV(gong_major, 0), GONG_NR_DEVS);
}
MODULE_LICENSE("GPL");
module_init(gong_init);
module_exit(gong_exit);
gong_kfifo_test.c
#include
#include
#include
#include
#include
#include
#include
#define GONG_KFIFO_MAGIC 'k'
/* Please use a different 8-bit number in your code */
#define GONG_KFIFO_SIZE _IO(GONG_KFIFO_MAGIC, 0)
#define GONG_KFIFO_RESET _IO(GONG_KFIFO_MAGIC, 1)
int main()
{
int GONGtest;
int code;
char input;
char string[64];
if ((GONGtest = open("/dev/GONG_kfifo", O_RDWR )) < 0 ) {
printf("cannot open GONG_kfifo!\n");
exit(1);
}
if ( lseek(GONGtest,0,SEEK_SET) == -1) printf("GONG_kfifo: the module can not lseek!\n");
for ( ; input != 'q' ; getchar()) {
printf("please input the command :");
input= getchar();
printf("cho: %c over\n", input);
switch(input) {
//#if 1
case '1':
if ((code=ioctl( GONGtest , GONG_KFIFO_SIZE , NULL ) ) < 0) printf("GONG_kfifo: ioctl GONG_KFIFO_SIZE error! code=%d \n",code);
else printf("GONG_kfifo: ioctl GONG_KFIFO_SIZE ok! len=%d \n",code);
break;
case '2':
if ((code=ioctl( GONGtest , GONG_KFIFO_RESET , NULL ) ) < 0) printf("GONG_kfifo: GONG_KFIFO_RESET error! code=%d \n",code);
else printf("GONG_kfifo: GONG_KFIFO_RESET ok! code=%d \n",code);
break;
case '3':
printf("please input your string :");
input = getchar();
gets(string);
printf("string is %s \n", string);
if(write(GONGtest, string, strlen(string)) < 0)
{
printf("write error \n");
exit(1);
}
break;
case '4':
if(read(GONGtest, string, 64) < 0)
{
printf("read error \n");
exit(1);
}
else
{
printf("string is %s \n", string);
}
break;
//#endif
case 'q':
break;
default: /* redundant, as cmd was checked against MAXNR */
printf("GONG_kfifo: Invalid input ! \n");
}
}
close(GONGtest);
exit(0);
}
Arm平台测试:
[root@FriendlyARM /udisk]# insmod gong_spinlock.ko
1
2
3
4
[root@FriendlyARM /udisk]# cat /proc/devices |more
Character devices:
1 mem
4 /dev/vc/0
4 tty
5 /dev/tty
5 /dev/console
5 /dev/ptmx
7 vcs
10 misc
13 input
14 sound
21 sg
29 fb
81 video4linux
89 i2c
90 mtd
116 alsa
128 ptm
136 pts
180 usb
188 ttyUSB
189 usb_device
204 s3c2410_serial
253 gong_buf
254 rtc
Block devices:
259 blkext
7 loop
8 sd
31 mtdblock
65 sd
66 sd
67 sd
68 sd
69 sd
70 sd
71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
[root@FriendlyARM /udisk]# mknod /dev/GONG_kfifo c 253 0
[root@FriendlyARM /udisk]# ./test
GONG_kfifo: the module can not lseek!
please input the command :1
cho: 1 over
GONG_kfifo: ioctl GONG_KFIFO_SIZE ok! len=0
please input the command :2
cho: 2 over
GONG_kfifo: GONG_KFIFO_RESET ok! code=0
please input the command :3
cho: 3 over
please input your string :gongzhihahh
string is gongzhihahh
"test" did write 11 bytes
please input the command :1
cho: 1 over
GONG_kfifo: ioctl GONG_KFIFO_SIZE ok! len=11
please input the command :4
cho: 4 over
"test" did read 11 bytes
string is gongzhihahh
please input the command :1
cho: 1 over
GONG_kfifo: ioctl GONG_KFIFO_SIZE ok! len=0
please input the command :5
cho: 5 over
GONG_kfifo: Invalid input !!
please input the command :q
cho: q over
[root@FriendlyARM /udisk]#