栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

同步---信号量

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

同步---信号量

信号量
  • 1 信号量
  • 2 驱动程序和测试程序
  • 3 内核的具体实现
  • 总结

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 okrn");
    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 okrn");
    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_writern");
    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_readrn");     
    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 exitn");
    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 
#include 
#include 
#include 
#include 
#include 


#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 errorrn");
		return -1;
    }	
	else {
		printf("open successern");
	}
    
    w_len = write(fd,buf, DATA_NUM);
    if(w_len==-1)
    {
        perror("write errorn");
        return -1;
    }
    sleep(5);
    printf("write len:%dn",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,这个时候有进程调度,进程调度开销比较大,并且不能在中断处理程序中使用信号量,因为信号量会睡眠。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/845450.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号