同步---信号量

信号量

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

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.oKERNELDIR := /lib/modules/$(shell uname -r)/buildall default:modules
install:modules_installmodules modules_install help clean:$(MAKE) -C $(KERNELDIR) M=$(shell pwd) $@test:test.cgcc $^ -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,这个时候有进程调度,进程调度开销比较大,并且不能在中断处理程序中使用信号量,因为信号量会睡眠。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379570.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java Float类floatToIntBits()方法与示例

Float类floatToIntBits()方法 (Float class floatToIntBits() method) floatToIntBits() method is available in java.lang package. floatToIntBits()方法在java.lang包中可用。 floatToIntBits() method follows IEEE 754 floating-point standards and according to standa…

解释三度带和六度带的概念以及各坐标系如何定义

★ 地形图坐标系&#xff1a;我国的地形图采用高斯&#xff0d;克吕格平面直角坐标系。在该坐标系中&#xff0c;横轴&#xff1a;赤道&#xff0c;用&#xff39;表示&#xff1b;纵轴&#xff1a;中央经线&#xff0c;用&#xff38;表示&#xff1b;坐标原点&#xff1a;中央…

0-1背包问题(物品不可分割)

问题背景&#xff1a; 所谓“钟点秘书”&#xff0c;是指年轻白领女性利用工余时间为客户提供秘书服务&#xff0c;并按钟点收取酬金。“钟点秘书”为客户提供有偿服务的方式一般是&#xff1a;采用电话、电传、上网等“遥控”式 服务&#xff0c;或亲自到客户公司处理部分业务…

算法---KMP算法

字符串1 KMP算法状态机概述构建状态转移1 KMP算法 原文链接&#xff1a;https://zhuanlan.zhihu.com/p/83334559 先约定&#xff0c;本文用pat表示模式串&#xff0c;长度为M&#xff0c;txt表示文本串&#xff0c;长度为N&#xff0c;KMP算法是在txt中查找子串pat&#xff0…

cache初接触,并利用了DataView

我们在写代码的时候,如果数据控件要获得数据,一般方法,Conn.Open();OleDbCommand cmd;cmd new OleDbCommand(sql, Conn);GridView1.DataSource dbcenter.accessGetDataSet(sql);GridView1.DataBind();Conn.close();但如果多个数据控件要绑定数据,则比较频繁打开数据库,效率一…

Java ByteArrayInputStream reset()方法及示例

ByteArrayInputStream类reset()方法 (ByteArrayInputStream Class reset() method) reset() method is available in java.util package. reset()方法在java.util包中可用。 reset() method is used to reset this ByteArrayInputStream to the last time marked position and …

回文数猜想

问题描述&#xff1a; 一个正整数&#xff0c;如果从左向右读&#xff08;称之为正序数&#xff09;和从右向左读&#xff08;称之为倒序数&#xff09;是一样的&#xff0c;这样的数就叫回文数。任取一个正整数&#xff0c;如果不是回文数&#xff0c;将该数与他的倒序数相加…

文件上传 带进度条(多种风格)

文件上传 带进度条 多种风格 非常漂亮&#xff01; 友好的提示 以及上传验证&#xff01; 部分代码&#xff1a; <form id"form1" runat"server"><asp:ScriptManager ID"scriptManager" runat"server" EnablePageMethods&quo…

同步---自旋锁

1 自旋锁的基本概念 自旋锁最多只能被一个可执行线程持有&#xff0c;如果一个执行线程试图获得一个已经被使用的自旋锁&#xff0c;那么该线程就会一直进行自旋&#xff0c;等待锁重新可用。在任何时刻&#xff0c;自旋锁都可以防止多余一个的执行线程同时进入临界区。 Linu…

实习日志----4.播放时段参数设置

由于客户在下发广告时&#xff0c;一则广告可在多个时段播放&#xff0c;这就需要设置多个播放时段的参数。 但在这种情况下&#xff0c;我并不知道用户每次需要下发几个时段&#xff0c;所以前台不能设定死。 因此我要实现这么一个功能&#xff0c;让用户根据自己的需要来动态…

线性插值算法实现图像_C程序实现插值搜索算法

线性插值算法实现图像Problem: 问题&#xff1a; We are given an array arr[] with n elements and an element x to be searched amongst the elements of the array. 给定一个数组arr []&#xff0c;其中包含n个元素和一个要在该数组的元素中搜索的元素x 。 Solution: 解&…

hdu 1197

地址&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1197 题意&#xff1a;求一个数转换成10&#xff0c;12&#xff0c;16进制后各个位上的数的和是否相等。 mark&#xff1a;模拟进制转换。 代码&#xff1a; #include <stdio.h>int zh(int a, int n) {int su…

linux系统编程---线程总结

线程总结1 线程的实现线程创建线程退出线程等待线程清理2 线程的属性线程的分离线程的栈地址线程栈大小线程的调度策略线程优先级3 线程的同步互斥锁读写锁条件变量信号量线程是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该进程中的全部系统资源&#xff0c;例…

博客上一些项目相关源码链接

GitHub&#xff1a;https://github.com/beyondyanyu/Sayingyy

重新开启Ctrl+Alt+Backspace快捷键

UBUNTU老用户知道CtrlAltBackspace这个快捷键是用来快速重启X的在9.04中被默认关闭了&#xff0c;那如何来打开它呢&#xff1f;在终端中输入&#xff1a;sudo gedit /etc/X11/xorg.conf在其中加入&#xff1a;Section “ServerFlags”Option “DontZap” “false”EndSection退…

Java LocalDate类| 带示例的getDayOfYear()方法

LocalDate类的getDayOfYear()方法 (LocalDate Class getDayOfYear() method) getDayOfYear() method is available in java.time package. getDayOfYear()方法在java.time包中可用。 getDayOfYear() method is used to get the day-of-year field value of this LocalDate obje…

火腿三明治定理

定理&#xff1a;任意给定一个火腿三明治&#xff0c;总有一刀能把它切开&#xff0c;使得火腿、奶酪和面包片恰好都被分成两等份。 而且更有趣的是&#xff0c;这个定理的名字真的就叫做“火腿三明治定理”&#xff08;ham sandwich theorem&#xff09;。它是由数学家亚瑟•斯…

如何给Linux操作系统(CentOS 7为例)云服务器配置环境等一系列东西

1.首先&#xff0c;你得去购买一个云服务器&#xff08;这里以阿里云学生服务器为例&#xff0c;学生必须实名认证&#xff09; 打开阿里云&#xff0c;搜索学生服务器点击进入即可 公网ip为连接云服务器的主机 自定义密码为连接云服务器是需要输入的密码 购买即可 点击云服…

Linux系统编程---I/O多路复用

文章目录1 什么是IO多路复用2 解决什么问题说在前面I/O模型阻塞I/O非阻塞I/OIO多路复用信号驱动IO异步IO3 目前有哪些IO多路复用的方案解决方案总览常见软件的IO多路复用方案4 具体怎么用selectpollepolllevel-triggered and edge-triggered状态变化通知(edge-triggered)模式下…

[转帖]纯属娱乐——变形金刚vs天网

[转帖]变形金刚2的影评-《变形金刚3 天网反击战》有一个问题困扰了我足足二十年&#xff1a;为什么汽车人要帮地球人&#xff1f;光用“所有有感知的生物都应享有自由”这个法则是根本说不过去的&#xff0c;因为猪也有感知&#xff0c;但人类就把猪圈养起来&#xff0c;随意杀…