IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 同步---信号量 -> 正文阅读

[系统运维]同步---信号量

1 信号量

Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其放到一个等待队列,然后让其睡眠,这时处理器去执行其他代码。当持有信号量的进程将信号量释放后,处于等待队列中的那个任务将被唤醒,并获得该信号量。
信号量定义在文件include/linux/semaphore.h中

/* Please don't access any members of this structure directly */
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中,下面的函数也定义在这个文件中

/* Please don't access any members of this structure directly */
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文件中

/**
 * down - acquire the semaphore
 * @sem: the semaphore to be acquired
 *
 * Acquires the semaphore.  If no more tasks are allowed to acquire the
 * semaphore, calling this function will put the task to sleep until the
 * semaphore is released.
 *
 * Use of this function is deprecated, please use down_interruptible() or
 * down_killable() instead.
 */
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;
	
	/* 将等待信号量的等待者加入到信号量的等待队列wait_list中 */
	list_add_tail(&waiter.list, &sem->wait_list);
	waiter.task = task;
	waiter.up = false;

	for (;;) {
		/* 检查有无信号打断 */
		if (signal_pending_state(state, task))
			goto interrupted;
		/* 检查timeout是否小于0 */
		if (unlikely(timeout <= 0))
			goto timed_out;
		/* 设置进程的状态 */
		__set_task_state(task, state);
		/* 解锁 */
		raw_spin_unlock_irq(&sem->lock);
		/* schedule_timeout用来让出CPU;在指定的时间用完以后或者其它事件到达并唤醒进程(比如接收了一个信号量)时,该进程才可以继续运行  */
		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文件中

/**
 * up - release the semaphore
 * @sem: the semaphore to release
 *
 * Release the semaphore.  Unlike mutexes, up() may be called from any
 * context and even by tasks which have never called down().
 */
void up(struct semaphore *sem)
{
	unsigned long flags;

	raw_spin_lock_irqsave(&sem->lock, flags);
	/* 判断信号的等待队列是否为空,为空直接让count加1 */
	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,这个时候有进程调度,进程调度开销比较大,并且不能在中断处理程序中使用信号量,因为信号量会睡眠。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-28 12:14:41  更:2022-04-28 12:17:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 19:27:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码