1 信号量
Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其放到一个等待队列,然后让其睡眠,这时处理器去执行其他代码。当持有信号量的进程将信号量释放后,处于等待队列中的那个任务将被唤醒,并获得该信号量。 信号量定义在文件include/linux/semaphore.h中
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
信号量可以同时允许任意数量的锁持有者,信号量同时允许的持有者数量可以在声明信号量时指定,这个值称为使用者数量。通常情况下,信号量和自旋锁一样,在一个时刻仅允许有一个锁持有者。当数量等于1,这样的信号量被称为二值信号量或者被称为互斥信号量;初始化时也可以把数量设置为大于1的非0值,这种情况,信号量被称为计数信号量,它允许在一个时刻至多有count个锁持有者。
信号量支持两个原子操作P()和V()。前者叫做测试操作,后者叫做增加操作,后来系统把这两种操作分别叫做down()和up(),Linux也遵从这种叫法。down()通过对信号量减1来请求一个信号量,如果减1结果是0或者大于0,那么就获得信号量锁,任务就可以进入临界区,如果结果是负的,那么任务会被放入等待队列。相反,当临界区的操作完成后,up()操作用来释放信号量,如果在该信号量上的等待队列不为空,那么处于队列中等待的任务被唤醒。
信号量的操作函数如下:
2 驱动程序和测试程序
在驱动中,我们仅允许一个进程打开设备,这个功能用互斥信号量来实现。 先执行:
sudo mknod /dev/hello c 232 0
驱动程序semaphore.c:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/semaphore.h>
#define BUFFER_MAX (64)
#define OK (0)
#define ERROR (-1)
struct cdev *gDev;
struct file_operations *gFile;
dev_t devNum;
unsigned int subDevNum = 1;
int reg_major = 232;
int reg_minor = 0;
char buffer[BUFFER_MAX];
struct semaphore sema;
int open_count = 0;
int hello_open(struct inode *p, struct file *f)
{
down(&sema);
if(open_count>=1)
{
up(&sema);
printk(KERN_INFO "device is busy,hello_open fail");
return -EBUSY;
}
open_count++;
up(&sema);
printk(KERN_INFO"hello_open ok\r\n");
return 0;
}
int hello_close(struct inode *inode,struct file *filp)
{
if(open_count!=1)
{
printk(KERN_INFO"something wrong,hello_close fail");
return -EFAULT;
}
open_count--;
printk(KERN_INFO"hello_close ok\r\n");
return 0;
}
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
int writelen =0;
printk(KERN_EMERG"hello_write\r\n");
writelen = BUFFER_MAX>s?s:BUFFER_MAX;
if(copy_from_user(buffer,u,writelen))
{
return -EFAULT;
}
return writelen;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
int readlen;
printk(KERN_EMERG"hello_read\r\n");
readlen = BUFFER_MAX>s?s:BUFFER_MAX;
if(copy_to_user(u,buffer,readlen))
{
return -EFAULT;
}
return readlen;
}
int hello_init(void)
{
devNum = MKDEV(reg_major, reg_minor);
if(OK == register_chrdev_region(devNum, subDevNum, "helloworld")){
printk(KERN_EMERG"register_chrdev_region ok \n");
}else {
printk(KERN_EMERG"register_chrdev_region error n");
return ERROR;
}
printk(KERN_EMERG" hello driver init \n");
gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
gFile->open = hello_open;
gFile->release = hello_close;
gFile->read = hello_read;
gFile->write = hello_write;
gFile->owner = THIS_MODULE;
cdev_init(gDev, gFile);
cdev_add(gDev, devNum, 3);
sema_init(&sema,1);
return 0;
}
void __exit hello_exit(void)
{
printk(KERN_INFO"hello driver exit\n");
cdev_del(gDev);
kfree(gDev);
unregister_chrdev_region(devNum, subDevNum);
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
测试程序test.c:
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>
#define DATA_NUM (64)
int main(int argc, char *argv[])
{
int fd, i;
int r_len, w_len;
fd_set fdset;
char buf[DATA_NUM]="hello world";
fd = open("/dev/hello", O_RDWR);
if(-1 == fd) {
perror("open file error\r\n");
return -1;
}
else {
printf("open successe\r\n");
}
w_len = write(fd,buf, DATA_NUM);
if(w_len==-1)
{
perror("write error\n");
return -1;
}
sleep(5);
printf("write len:%d\n",w_len);
close(fd);
return 0;
}
Makefile:
obj-m := semaphore.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
all default:modules
install:modules_install
modules modules_install help clean:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) $@
test:test.c
gcc $^ -o $@
执行命令:
make
make test
当我们同时打开两个测试时,只有一个能打开,另一个打开失败,实现了互斥访问。
3 内核的具体实现
信号量定义在文件include/linux/semaphore.h中,下面的函数也定义在这个文件中
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
初始化函数
static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
该初始化会将val值赋值给struct semaphore里的count,wait_list初始化为链表头,lock值设定为解锁状态,lock是自旋锁。
down函数的实现在kernel/locking/semaphore.c文件中
void down(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
__down(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
首先是raw_spin_lock_irqsave加锁,接着判断count是不是大于0,大于0就count就减去1,否则,转到__down函数执行
static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
TASK_UNINTERRUPTIBLE表示进程不可中断,MAX_SCHEDULE_TIMEOUT表示休眠时间
static inline int __sched __down_common(struct semaphore *sem, long state,
long timeout)
{
struct task_struct *task = current;
struct semaphore_waiter waiter;
list_add_tail(&waiter.list, &sem->wait_list);
waiter.task = task;
waiter.up = false;
for (;;) {
if (signal_pending_state(state, task))
goto interrupted;
if (unlikely(timeout <= 0))
goto timed_out;
__set_task_state(task, state);
raw_spin_unlock_irq(&sem->lock);
timeout = schedule_timeout(timeout);
raw_spin_lock_irq(&sem->lock);
if (waiter.up)
return 0;
}
timed_out:
list_del(&waiter.list);
return -ETIME;
interrupted:
list_del(&waiter.list);
return -EINTR;
}
semaphore_waiter 的实例表示信号的一个等待者
struct semaphore_waiter {
struct list_head list;
struct task_struct *task;
bool up;
};
list_head是一个双向链表。
__down会先将进程加入到信号的等待队列中,然后将进程设置为不可打断的睡眠状态,接着让出CPU,在指定的时间用完以后或者其它事件到达并唤醒进程,如果等待进程waiter的up不为真,将一直for循环,直到up为真,返回0。
所以down函数的功能就是先判断count是否大于0(即是否还有资源),如果大于0,减1,继续执行,否则就调用__down,将进程加入信号的等待队列中,一直for循环,直到up为真,然后继续执行。
up函数的实现也在kernel/locking/semaphore.c文件中
void up(struct semaphore *sem)
{
unsigned long flags;
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;
else
__up(sem);
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
首先判断信号的等待队列是否为空,为空直接让count加1,否则进入__up函数:
static noinline void __sched __up(struct semaphore *sem)
{
struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
struct semaphore_waiter, list);
list_del(&waiter->list);
waiter->up = true;
wake_up_process(waiter->task);
}
__up函数首先拿到等待该信号的第一个进程,在等待队列中删除该进程,并且将up置为true,最后唤醒该进程。
总结
信号量会让进程休眠,让出CPU,这个时候有进程调度,进程调度开销比较大,并且不能在中断处理程序中使用信号量,因为信号量会睡眠。
|