信号量
信号量分为用户信号量和内核信号量,用户信号量又分为POSIX信号量和SYTEM V 信号量,POSIX信号量又细分为无名信号量与有名信号量。所以总共有四种信号量,即无名信号量、有名信号量、SYTEM V信号量、内核信号量。
该文章中主要讲内核信号量的使用方法:
初始化
在linux/semaphore.h中
#include <linux/semaphore.h>
struct semaphore sem;
sema_init(&sem,1);
// 第一个参数是要操作的信号量
// 第二个参数是信号量计数的初始值
PV操作:
P操作指的是一个任务想要获取已经被占用的信号量时,信号量会将其放入一个等待队列中然后让其睡眠。
V操作指的是当持有信号量的进程将信号释放,处于等待队列中的一个任务将被唤醒(因为等待队列中不止一个任务),并让其获得信号量。
P操作相关函数:
// 如果可以获得信号量计数-1,无法则获得挂入等待队列睡眠
void down(struct semaphore *sem);
// 如果可以获得信号量计数-1,无法则获得挂入等待队列睡眠,可被打断
int __must_check down_interruptible(struct semaphore *sem);
// 如果可以获得信号量计数-1,无法则获得挂入等待队列睡眠,可被杀死
int __must_check down_killable(struct semaphore *sem);
// 尝试获取信号量,成功获得计数-1,返回0,失败返回非零,不会导致睡眠
int __must_check down_trylock(struct semaphore *sem);
// 如果可以获得信号量计数-1,无法获得加入等待队列等待,达到超时时间返回非0
int __must_check down_timeout(struct semaphore *sem,long jiffies);
V操作相关函数:
// 释放信号量,并唤醒等待队列中的一个任务
void up(struct semaphore *sem);
示例代码:
模块源文件sema.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/semaphore.h>
#define DEV_NAME "sema_test"
#define DEV_NUM (1)
static struct my_st
{
int major;
int minor;
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *device;
struct semaphore sema;
}*my;
static int sema_open(struct inode *inode,struct file *file)
{
printk("oepn()\r\n");
down(&my->sema);
return 0;
}
static int sema_release(struct inode *inode,struct file *file)
{
printk("release()\r\n");
up(&my->sema);
return 0;
}
static const struct file_operations fops =
{
.owner = THIS_MODULE,
.open = sema_open,
.release = sema_release,
};
static int __init msema_init(void)
{
int ret;
my = kzalloc(sizeof(struct my_st),GFP_KERNEL);
if(my == NULL)
return -ENOMEM;
sema_init(&my->sema,1);
ret = alloc_chrdev_region(&my->dev_num,0,DEV_NUM,DEV_NAME);
if(ret < 0)
goto alloc_chrdev_failed;
my->major = MAJOR(my->dev_num);
my->minor = MINOR(my->dev_num);
cdev_init(&my->cdev,&fops);
my->cdev.owner = THIS_MODULE;
ret = cdev_add(&my->cdev,my->dev_num,DEV_NUM);
if(ret < 0)
goto cdev_add_failed;
my->class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(my->class))
{
ret = PTR_ERR(my->class);
goto class_create_failed;
}
my->device = device_create(my->class,NULL,my->dev_num,NULL,DEV_NAME);
if(IS_ERR(my->device))
{
ret = PTR_ERR(my->device);
goto device_create_failed;
}
printk("sema module was installed !!!\r\n");
printk("major dev number is %d \r\n",my->major);
return 0;
device_create_failed:
class_destroy(my->class);
class_create_failed:
cdev_del(&my->cdev);
cdev_add_failed:
unregister_chrdev_region(my->dev_num,DEV_NUM);
alloc_chrdev_failed:
kfree(my);
return ret;
}
static void __exit msema_exit(void)
{
device_destroy(my->class,my->dev_num);
class_destroy(my->class);
cdev_del(&my->cdev);
unregister_chrdev_region(my->dev_num,DEV_NUM);
kfree(my);
}
module_init(msema_init);
module_exit(msema_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Chen");
Makefile 文件
ifneq ($(KERNELRELEASE),)
obj-m:=sema.o
else
CURRENT_DIR:=$(shell pwd)
KERNEL_DIR:=/lib/modules/$(shell uname -r)/build
ccflags-y:=-std=gnu99 -Wno-declaration-after-statement
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
endif
测试文件
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
fd = open("/dev/sema_test",O_RDWR);
if(fd < 0)
{
perror("");
exit(1);
}
printf("1\r\n");
sleep(15);
printf("2\r\n");
close(fd);
exit(0);
}
编译程序,然后切换到root执行,再开启一个新的终端也执行,可以看到,只有在第一个app执行后第二个app才能获取信号量才能执行。并且在过程中无法使用ctrl+c结束,若有需要可以使用down的其他类型函数。