信号量、互斥锁、并发机制选择原则

一、信号量:基于阻塞的并发控制机制

a.定义信号量

struct semaphore sem;

b.初始化信号量

void sema_init(struct semaphore *sem, int val);

c.获得信号量P

int down(struct semaphore *sem);//深度睡眠

int down_interruptible(struct semaphore *sem);//浅度睡眠

d.释放信号量V

void up(struct semaphore *sem);

复制代码
#include <linux/semaphore.h>

适用场合:任务上下文之间且临界区执行时间较长时的互斥或同步问题

实现代码

cpp 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/ioctl.h>

#include "mychar.h"

#define BUF_LEN 100

int major = 11;					//主设备号
int minor = 0;					//次设备号
int char_num = 1;				//设备号数量

struct mychar_dev 
{
	struct cdev mydev;
	char mydev_buf[BUF_LEN];
	int curlen;
	struct semaphore sem;

	wait_queue_head_t rq;
	wait_queue_head_t wq;

	struct fasync_struct *pasync_obj;
};
struct mychar_dev gmydev;

int mychar_open (struct inode *pnode, struct file *pfile)//打开设备
{
	pfile->private_data = (void *) (container_of(pnode->i_cdev, struct mychar_dev, mydev));
	return 0;
}

int mychar_close(struct inode *pnode, struct file *pfile)//关闭设备
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	if(pmydev->pasync_obj != NULL) {
		fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
	}
	
	return 0;
}

ssize_t mychar_read (struct file *pfile, char __user *puser, size_t count, loff_t *p_pos) {

	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	down(&pmydev->sem);

	/* 判断是否有数据可读 */
	if(pmydev->curlen <= 0) {

		if(pfile->f_flags & O_NONBLOCK) { //非阻塞
			up(&pmydev->sem);
			printk("O_NONBLOCK Not Data Read\n");
			return -1;

		} else { //阻塞

			up(&pmydev->sem);
			/* 睡眠 当curlen>0 时返回 */
			ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
			if(ret) {
				return -ERESTARTSYS;
			}
		}
		down(&pmydev->sem);
	}

	// 确定要读取的数据长度,如果请求大于设备当前数据长度,则读取全部可用数据
	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	}
	else {
		size = count;
	}

	// 将设备数据复制到用户空间缓冲区
	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if(ret) {
		up(&pmydev->sem);
		printk("copy_to_user failed\n");
		return -1;
	}

	// 移动设备内部缓冲区,去除已读取的数据
	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;
	up(&pmydev->sem);

	wake_up_interruptible(&pmydev->wq);
	
	// 返回实际读取的字节数
	return size;
}

ssize_t mychar_write (struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos) {

	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	down(&pmydev->sem);
	if(pmydev->curlen >= BUF_LEN) {

		if(pfile->f_flags & O_NONBLOCK) { //非阻塞
			up(&pmydev->sem);
			printk("O_NONBLOCK Can Not Write Data\n");
			return -1;

		} else { //阻塞
			up(&pmydev->sem);
			ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
			if(ret) {
				return -ERESTARTSYS;
			}
			down(&pmydev->sem);
		}
		
	}


	// 确定要写入的数据长度,如果请求大于设备缓冲区剩余空间,则写入剩余空间大小
	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	}
	else {
		size = count;
	}

	// 从用户空间复制数据到设备缓冲区
	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if(ret) {
		up(&pmydev->sem);
		printk("copy_from_user failed\n");
		return -1;
	}

	// 更新设备缓冲区中的数据长度
	pmydev->curlen += size;
	up(&pmydev->sem);


	/* 唤醒读阻塞 */
	wake_up_interruptible(&pmydev->rq);

	if(pmydev->pasync_obj != NULL) {
		kill_fasync(&pmydev->pasync_obj, SIGIO,POLL_IN);
	}

	 // 返回实际写入的字节数
	return size;
}

long mychar_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg) 
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int __user *pret = (int *)arg;
	int maxlen = BUF_LEN;
	int ret = 0;

	switch(cmd) {
	case MYCHAR_IOCTL_GET_MAXLEN:
		ret = copy_to_user(pret, &maxlen, sizeof(int));
		if(ret) {
			printk("copy_from_user failed\n");
			return -1;
		}
		break;

	case MYCHAR_IOCTL_GET_CURLEN:
		down(&pmydev->sem);
		ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
		up(&pmydev->sem);
		if(ret) {
			printk("copy_from_user failed\n");
			return -1;
		}
		break;

	default:
		printk("The is a know\n");
		return -1;
	}
	return 0;
}

unsigned int mychar_poll(struct file *pfile, poll_table *ptb) {

	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);
	poll_wait(pfile, &pmydev->wq, ptb);

	down(&pmydev->sem);
	if(pmydev->curlen > 0) {
		mask |= POLLIN | POLLRDNORM;
	}
	if(pmydev->curlen < BUF_LEN) {
		mask |= POLLOUT | POLLWRNORM;
	}
	up(&pmydev->sem);

	return mask;
}

int mychar_fasync(int fd, struct file *pfile, int mode) {
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
	.poll = mychar_poll,
	.fasync = mychar_fasync,
};

int __init mychar_init(void) 
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 手动申请设备号 */
	ret = register_chrdev_region(devno, char_num, "mychar");
	if (ret) {
		/* 动态申请设备号 */
		ret = alloc_chrdev_region(&devno, minor, char_num, "mychar");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		/*申请成功 更新设备号*/
		major = MAJOR(devno);
	}
	
	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 将struct cdev对象添加到内核对应的数据结构中 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, char_num);

	/* 初始化 */
	init_waitqueue_head(&gmydev.rq);
	init_waitqueue_head(&gmydev.wq);

	sema_init(&gmydev.sem, 1);

	return 0;
}

void __exit mychar_exit(void) 
{

	dev_t devno = MKDEV(major, minor);
	printk("exit %d\n", devno);
	
	/* 从内核中移除一个字符设备 */
	cdev_del(&gmydev.mydev);

	/* 回收设备号 */
	unregister_chrdev_region(devno, char_num);

}

MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);

二、互斥锁:基于阻塞的互斥机制

a.初始化

struct mutex my_mutex;

mutex_init(&my_mutex);

b.获取互斥体

void mutex_lock(struct mutex *lock);

c.释放互斥体

void mutex_unlock(struct mutex *lock);

  1. 定义对应类型的变量
  2. 初始化对应变量

P/加锁

临界区

V/解锁

复制代码
#include <linux/mutex.h>

适用场合:任务上下文之间且临界区执行时间较长时的互斥问题

实现代码

cpp 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/ioctl.h>

#include "mychar.h"

#define BUF_LEN 100

int major = 11;					//主设备号
int minor = 0;					//次设备号
int char_num = 1;				//设备号数量

struct mychar_dev 
{
	struct cdev mydev;
	char mydev_buf[BUF_LEN];
	int curlen;
	struct mutex lock;

	wait_queue_head_t rq;
	wait_queue_head_t wq;

	struct fasync_struct *pasync_obj;
};
struct mychar_dev gmydev;

int mychar_open (struct inode *pnode, struct file *pfile)//打开设备
{
	pfile->private_data = (void *) (container_of(pnode->i_cdev, struct mychar_dev, mydev));
	return 0;
}

int mychar_close(struct inode *pnode, struct file *pfile)//关闭设备
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	if(pmydev->pasync_obj != NULL) {
		fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
	}
	
	return 0;
}

ssize_t mychar_read (struct file *pfile, char __user *puser, size_t count, loff_t *p_pos) {

	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	mutex_lock(&pmydev->lock);

	/* 判断是否有数据可读 */
	if(pmydev->curlen <= 0) {

		if(pfile->f_flags & O_NONBLOCK) { //非阻塞
			mutex_unlock(&pmydev->lock);
			printk("O_NONBLOCK Not Data Read\n");
			return -1;

		} else { //阻塞

			mutex_unlock(&pmydev->lock);
			/* 睡眠 当curlen>0 时返回 */
			ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
			if(ret) {
				return -ERESTARTSYS;
			}
		}
		mutex_lock(&pmydev->lock);
	}

	// 确定要读取的数据长度,如果请求大于设备当前数据长度,则读取全部可用数据
	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	}
	else {
		size = count;
	}

	// 将设备数据复制到用户空间缓冲区
	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if(ret) {
		mutex_unlock(&pmydev->lock);
		printk("copy_to_user failed\n");
		return -1;
	}

	// 移动设备内部缓冲区,去除已读取的数据
	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;
	mutex_unlock(&pmydev->lock);

	wake_up_interruptible(&pmydev->wq);
	
	// 返回实际读取的字节数
	return size;
}

ssize_t mychar_write (struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos) {

	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	mutex_lock(&pmydev->lock);
	if(pmydev->curlen >= BUF_LEN) {

		if(pfile->f_flags & O_NONBLOCK) { //非阻塞
			mutex_unlock(&pmydev->lock);
			printk("O_NONBLOCK Can Not Write Data\n");
			return -1;

		} else { //阻塞
			mutex_unlock(&pmydev->lock);
			ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
			if(ret) {
				return -ERESTARTSYS;
			}
			mutex_lock(&pmydev->lock);
		}
		
	}


	// 确定要写入的数据长度,如果请求大于设备缓冲区剩余空间,则写入剩余空间大小
	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	}
	else {
		size = count;
	}

	// 从用户空间复制数据到设备缓冲区
	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if(ret) {
		mutex_unlock(&pmydev->lock);
		printk("copy_from_user failed\n");
		return -1;
	}

	// 更新设备缓冲区中的数据长度
	pmydev->curlen += size;
	mutex_unlock(&pmydev->lock);


	/* 唤醒读阻塞 */
	wake_up_interruptible(&pmydev->rq);

	if(pmydev->pasync_obj != NULL) {
		kill_fasync(&pmydev->pasync_obj, SIGIO,POLL_IN);
	}

	 // 返回实际写入的字节数
	return size;
}

long mychar_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg) 
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int __user *pret = (int *)arg;
	int maxlen = BUF_LEN;
	int ret = 0;

	switch(cmd) {
	case MYCHAR_IOCTL_GET_MAXLEN:
		ret = copy_to_user(pret, &maxlen, sizeof(int));
		if(ret) {
			printk("copy_from_user failed\n");
			return -1;
		}
		break;

	case MYCHAR_IOCTL_GET_CURLEN:
		mutex_lock(&pmydev->lock);
		ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
		mutex_unlock(&pmydev->lock);
		if(ret) {
			printk("copy_from_user failed\n");
			return -1;
		}
		break;

	default:
		printk("The is a know\n");
		return -1;
	}
	return 0;
}

unsigned int mychar_poll(struct file *pfile, poll_table *ptb) {

	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);
	poll_wait(pfile, &pmydev->wq, ptb);

	mutex_lock(&pmydev->lock);
	if(pmydev->curlen > 0) {
		mask |= POLLIN | POLLRDNORM;
	}
	if(pmydev->curlen < BUF_LEN) {
		mask |= POLLOUT | POLLWRNORM;
	}
	mutex_unlock(&pmydev->lock);

	return mask;
}

int mychar_fasync(int fd, struct file *pfile, int mode) {
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
	.poll = mychar_poll,
	.fasync = mychar_fasync,
};

int __init mychar_init(void) 
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 手动申请设备号 */
	ret = register_chrdev_region(devno, char_num, "mychar");
	if (ret) {
		/* 动态申请设备号 */
		ret = alloc_chrdev_region(&devno, minor, char_num, "mychar");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		/*申请成功 更新设备号*/
		major = MAJOR(devno);
	}
	
	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 将struct cdev对象添加到内核对应的数据结构中 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, char_num);

	/* 初始化 */
	init_waitqueue_head(&gmydev.rq);
	init_waitqueue_head(&gmydev.wq);

	mutex_init(&gmydev.lock);

	return 0;
}

void __exit mychar_exit(void) 
{

	dev_t devno = MKDEV(major, minor);
	printk("exit %d\n", devno);
	
	/* 从内核中移除一个字符设备 */
	cdev_del(&gmydev.mydev);

	/* 回收设备号 */
	unregister_chrdev_region(devno, char_num);

}

MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);

三、选择并发控制机制的原则

  1. 不允许睡眠的上下文需要采用忙等待类,可以睡眠的上下文可以采用阻塞类。在异常上下文中访问的竞争资源一定采用忙等待类。
  2. 临界区操作较长的应用建议采用阻塞类,临界区很短的操作建议采用忙等待类。
  3. 中断屏蔽仅在有与中断上下文共享资源时使用。
  4. 共享资源仅是一个简单整型量时用原子变量
相关推荐
yuxb7315 分钟前
Linux 文本处理与 Shell 编程笔记:正则表达式、sed、awk 与变量脚本
linux·笔记·正则表达式
刘一说26 分钟前
CentOS 系统 Java 开发测试环境搭建手册
java·linux·运维·服务器·centos
wdxylb6 小时前
云原生俱乐部-shell知识点归纳(1)
linux·云原生
飞雪20077 小时前
Alibaba Cloud Linux 3 在 Apple M 芯片 Mac 的 VMware Fusion 上部署的完整密码重置教程(二)
linux·macos·阿里云·vmware·虚拟机·aliyun·alibaba cloud
路溪非溪7 小时前
关于Linux内核中头文件问题相关总结
linux
Lovyk10 小时前
Linux 正则表达式
linux·运维
Fireworkitte11 小时前
Ubuntu、CentOS、AlmaLinux 9.5的 rc.local实现 开机启动
linux·ubuntu·centos
sword devil90011 小时前
ubuntu常见问题汇总
linux·ubuntu
ac.char11 小时前
在CentOS系统中查询已删除但仍占用磁盘空间的文件
linux·运维·centos
淮北也生橘1213 小时前
Linux的ALSA音频框架学习笔记
linux·笔记·学习