Notes14同步和互斥,ioctl函数

Posted 码农编程录

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Notes14同步和互斥,ioctl函数相关的知识,希望对你有一定的参考价值。


1.同步和互斥:避免多进程同时操作设备的意外情况


如上在hello_chr.c中,第一步释放存储空间,先make再insmod…ko进内核。


执行如下后如上出现段错误。

如下bash相当于上面的echo。

如下连着如上,bash第一步是释放存储空间。读写时不采取同步措施,会出错。

1.1 hello_chr_locked.c:还有读写锁、自旋锁、seqlock、原子变量

/*
	信号量
	sema_init()    初始化信号量
	down_interruptible()    获取信号量
	up()    释放信号量
	
	互斥锁
	mutex_init()
	mutex_lock_interruptible()   获取
	mutex_unlock()
*/
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/slab.h>
#include<linux/uaccess.h>
#include<linux/jiffies.h>
#include<linux/sched.h>
#include<linux/semaphore.h>
#include<linux/mutex.h>
#define LOCK_USE 1 //0:semaphore,1:mutex
#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2

int hello_major = HELLO_MAJOR;
int hello_minor = 0;
dev_t devt;      //高12位是主设备号,低20位是次设备号
int hello_nr_devs = HELLO_NR_DEVS;

module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);
module_param(hello_nr_devs, int, S_IRUGO);

struct hello_char_dev		//实际的字符设备结构,类似于面向对象的继承
	struct cdev cdev;
	char *c;
	int n;
	struct semaphore sema;
	struct mutex mtx;
;
struct hello_char_dev *hc_devp;
struct class *hc_cls;

//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
int hc_open(struct inode *inode, struct file *filp)

	struct hello_char_dev *hc_dev;
	printk(KERN_INFO "%s open \\n",current->comm);
	hc_dev = container_of(inode->i_cdev,struct hello_char_dev,cdev);  //获取设备结构体的地址
	filp->private_data = hc_dev;		//将设备结构地址放到文件描述符结构的私有数据中

	return 0;


//111111111111111111111111111111111111111111111111111111111111111111111111111111111111
ssize_t hc_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)

	ssize_t retval=0;
	struct hello_char_dev *hc_dev=filp->private_data;
	printk(KERN_INFO "read hc_dev %p\\n",hc_dev);
	
	if(*f_pos >= hc_dev->n)
		goto out;
	if(*f_pos + count > hc_dev->n)
		count = hc_dev->n - *f_pos;
	
	if(copy_to_user(buf,hc_dev->c,count))
	
		retval = -EFAULT;
		goto out;
	

	*f_pos += count;
	return count;	
out:
	return retval;


//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
ssize_t hc_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)

	struct hello_char_dev *hc_dev=filp->private_data;
	int retval = -ENOMEM;
	unsigned long jiff1;
	printk(KERN_INFO "%s write begin\\n",current->comm);
	#if (LOCK_USE==0)
	if(down_interruptible(&hc_dev->sema))		//-EINTR   先获得信号量或互斥锁,成功获取返回0,执行后面操作。获取不到进行等待,让进程进入休眠状态。如果我们给它发送一个信号,它就会返回一个非0值,这个值就是EINTR,如果接到EINTR就返回ERESTARTSYS,重新获取信号量。如果接到信号后退出,下行就返回EINTR。
		return -ERESTARTSYS;
	printk(KERN_INFO "%s get sema\\n",current->comm);
	#endif
	#if (LOCK_USE==1)
	if(mutex_lock_interruptible(&hc_dev->mtx))   //-EINTR
		return -ERESTARTSYS;
	printk(KERN_INFO "%s get mutex\\n",current->comm);
	#endif

//获得到信号量或互斥锁后就可正常执行如下写操作。
	kfree(hc_dev->c);
	hc_dev->c=NULL;
	hc_dev->n=0;
	printk(KERN_INFO"%s 1",current->comm);
	jiff1=jiffies;
	while(jiffies-jiff1<HZ);
	hc_dev->c = kzalloc(count,GFP_KERNEL);
	if(!hc_dev->c)
		goto out;
	printk(KERN_INFO"%s 2 addr:%p",current->comm,hc_dev->c);
	jiff1=jiffies;
	while(jiffies-jiff1<HZ);
	printk(KERN_INFO"%s 3 addr:%p",current->comm,hc_dev->c);
	if(copy_from_user(hc_dev->c,buf,count))
	
		retval = -EFAULT;
		goto fail_copy;
	
	hc_dev->n = count;
	
//如上写操作执行完后,如下我们需要释放掉互斥量或互斥锁。
	#if (LOCK_USE==0)
	up(&hc_dev->sema);
	printk(KERN_INFO "%s up sema\\n",current->comm);
	#endif
	#if (LOCK_USE==1)
	mutex_unlock(&hc_dev->mtx);
	printk(KERN_INFO "%s unlock mutex\\n",current->comm);
	#endif
	printk(KERN_INFO"%s write done",current->comm);
	return count;	 
fail_copy:
	kfree(hc_dev->c);
out:
	#if (LOCK_USE==0)
	up(&hc_dev->sema);
	#endif
	#if (LOCK_USE==1)
	mutex_unlock(&hc_dev->mtx);
	#endif
	return retval;					//不能返回0,否则会不停的写


//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111
int hc_release(struct inode *inode, struct file *filp)

	printk(KERN_INFO "%s release\\n",current->comm);
	return 0;


//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
struct file_operations hc_fops = 		//字符设备的操作函数
	.owner =    THIS_MODULE,
	.read =     hc_read,
	.write =    hc_write,
	.open =     hc_open,
	.release =  hc_release,
;

//1111111111111111111111111111111111111111111111111111111111111111111111
static int __init hello_init(void)	

	int ret,i;
	printk(KERN_INFO "---BEGIN HELLO LINUX MODULE---\\n");
	if(hello_major)
		devt=MKDEV(hello_major,hello_minor);
		ret=register_chrdev_region(devt,hello_nr_devs,"hello_chr");	//使用指定的设备号分配
	
	else
		ret = alloc_chrdev_region(&devt,hello_minor,hello_nr_devs,"hello_chr");//动态分配主设备号
		hello_major = MAJOR(devt);
	
	if (ret < 0) 
		printk(KERN_WARNING "hello: can't get major %d\\n", hello_major);
		goto fail;
	
	
	hc_devp = kzalloc(sizeof(struct hello_char_dev)*hello_nr_devs,GFP_KERNEL);  //给字符设备分配空间,这里hello_nr_devs为2
	if(!hc_devp)
	
		printk(KERN_WARNING "alloc mem failed");
		ret = -ENOMEM;
		goto failure_kzalloc;		// 内核常用goto处理错误
	
	
	for(i=0;i<hello_nr_devs;i++)	
	#if (LOCK_USE==0)
		sema_init(&hc_devp[i].sema,1);  // 初始化信号量,第一个参数:信号量地址。第二个参数:信号量个数,这里设为1,当一个进程获得,其他进程等待。
	 // 信号量可有多个,有一个进程来获取,信号量个数减1。直到信号量减到0,进程无法获取信号量。当其他进程释放信号量后,被休眠的进程才可以获得信号量。
	#elif (LOCK_USE==1)
		mutex_init(&hc_devp[i].mtx);   // 初始化互斥量, 只有一个,一个进程获得互斥锁,其他进程只能等待。上面信号量初始化为1和这里互斥锁类似。
	#endif 

		cdev_init(&hc_devp[i].cdev,&hc_fops);		// 初始化字符设备结构
		hc_devp[i].cdev.owner = THIS_MODULE;
		ret = cdev_add(&hc_devp[i].cdev,MKDEV(hello_major,hello_minor+i),1);
		if(ret)
		
			printk(KERN_WARNING"fail add hc_dev%d",i);
		
		
	
	hc_cls = class_create(THIS_MODULE,"hc_dev");
	if(!hc_cls)
	
		printk(KERN_WARNING"fail create class");
		ret = PTR_ERR(hc_cls);
		goto failure_class;
	
	for(i=0;i<hello_nr_devs;i++)
		device_create(hc_cls,NULL,MKDEV(hello_major,hello_minor+i),NULL,"hc_dev%d",i);
		
	printk(KERN_INFO "---END HELLO LINUX MODULE---\\n");
	return 0;

failure_class:
	kfree(hc_devp);
failure_kzalloc:		
	unregister_chrdev_region(devt,hello_nr_devs);
fail:
	return ret;	//返回错误,模块无法正常加载


//111111111111111111111111111111111111111111111111111111111111111111111111111111111
static void __exit hello_exit(void)

	int i;	
	for(i=0;i<hello_nr_devs;i++)
	
		device_destroy(hc_cls,MKDEV(hello_major,hello_minor+i));
	
	class_destroy(hc_cls);
	for(i=0;i<hello_nr_devs;i++)
		cdev_del(&hc_devp[i].cdev);
	kfree(hc_devp);
	unregister_chrdev_region(devt,hello_nr_devs);	//移除模块时释放设备号		
	printk(KERN_INFO "GOODBYE LINUX\\n");


//111111111111111111111111111111111111111111111111111111111111111111111111111111111
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");//许可 GPL、GPL v2、Dual MPL/GPL、Proprietary(专有)等,没有内核会提示
MODULE_AUTHOR("KGZ");		//作者
MODULE_VERSION("V1.0");  	//版本

同理make后insmod,再运行writeloop和echo,dmesg。上面只是在写时加入互斥锁和信号量,读时也要加入。

2.实现设备驱动的ioctl函数:对一个设备文件除了读写操作外,可能还有弹出光驱,弹出介质,修改设置等等,这时就用到驱动程序的ioctl函数

2.1 hello_chr_locked.h:正常情况要不都按值传递,要不都用指针,这里只是演示

#ifndef _HELLO_CHR_LOCKED_H_
#define _HELLO_CHR_LOCKED_H_

#define HC_IOC_MAGIC 0x81  //type(0x81未被使用) //内核源码里Documentation/userspace-api/ioctl/ioctl-number.rst
#define HC_IOC_RESET		_IO(HC_IOC_MAGIC,0)  //序号0,作用是清空我们分配的空间
#define HC_IOCP_GET_LENS	_IOR(HC_IOC_MAGIC,1,int)	//通过指针返回字符串长度   都是ioctl函数第三个参数
#define HC_IOCV_GET_LENS	_IO(HC_IOC_MAGIC,2)		//通过返回值返回字符串长度
#define HC_IOCP_SET_LENS 	_IOW(HC_IOC_MAGIC,3,int)	//通过指针设置字符串长度
#define HC_IOCV_SET_LENS	_IO(HC_IOC_MAGIC,4)		//通过值设置字符串长度
#define HC_IOC_MAXNR 4  //最大命令编号
#endif

2.2 hello_chr_locked.c:ioctl执行硬件控制,除了读写外的其他操作,比如锁门、弹出介质、设置波特率、设置比特位等。

/*
// 如下第二个参数是ioctl命令,第三个参数是对应命令的参数。
long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg)  

// 如下direction指明读/写,size指传入参数的数据大小,type唯一标识ioctl命令,number是命令编号
ioctl cmd构成:direction(方向) size(数据大小) type(幻数)   number(序数) 
		 |	   2bits	|	14bits		|	8bits	|	 8bits 	 |

宏:_IO(type,nr) _IOR(type,nr,size) _IOW(type,nr,size) _IOWR(type,nr,size) //为了构造上面的命令
	_IOC_DIR(nr) _IOC_TYPE(nr) _IOC_NR(nr) _IOC_SIZE(nr)   //从命令中提取对应字段

函数:  access_ok()				//检查用户空间地址是否OK
	如下两个函数是ioctl函数传入的数据量不大,数据量大的话用之前的copy_from/to_user
	put_user()和__put_user()	//向用户空间写数据,put_user安全即里面执行了access_ok操作
	get_user()和__get_user()	//从用户空间接收数据
	capable()	//检查进程是否有权限,因为ioctl要对硬件进行控制和更改,需要进程被授权操作权限。
*/
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/slab.h>
#include<linux/uaccess.h>
#include<linux/sched.h>
#include<linux/semaphore.h>
#include<linux/mutex.h>
#include "hello_chr_locked.h"  //自定义
#define LOCK_USE 1 //0:semaphore,1:mutex
#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2

int hello_major = HELLO_MAJOR;
int hello_minor = 0;
dev_t devt;      //高12位是主设备号,低20位是次设备号
int hello_nr_devs = HELLO_NR_DEVS;

module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);
module_param(hello_nr_devs, int, S_IRUGO);

struct hello_char_dev		//实际的字符设备结构,类似于面向对象的继承
	struct cdev cdev;
	char *c;
	int n;
	struct semaphore sema;
	struct mutex mtx;
;

struct hello_char_dev *hc_devp;
struct class *hc_cls;

//11111111111111111111111111111111111111111111111111111111111111111111111111111
int hc_open(struct inode *inode, struct file *filp)

	struct hello_char_dev *hc_dev;
	printk(KERN_INFO "%s open \\n",current->comm);
	hc_dev = container_of(inode->i_cdev,struct hello_char_dev,cdev);  //获取设备结构体的地址
	filp->private_data = hc_dev;		//将设备结构地址放到文件描述符结构的私有数据中

	return 0;


//11111111111111111111111111111111111111111111111111111111111111111111111111111111
ssize_t hc_read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)

	ssize_t retval=0;
	struct hello_char_dev *hc_dev=filp->private_data;
	//printk(KERN_INFO "read hc_dev %p\\n",hc_dev);
	
	if(*f_pos >= hc_dev->n)
		goto out;
	if(*f_pos + count > hc_dev->n)
		count = hc_dev->n - *f_pos;
	
	if(copy_to_user(buf,hc_dev->c,count))
	
		retval = -EFAULT;
		goto out;
	

	*f_pos += count;
	return count;	
out:
	return retval;


//11111111111111111111111111111111111111111111111111111111111111111111111111111111
ssize_t hc_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)

	以上是关于Notes14同步和互斥,ioctl函数的主要内容,如果未能解决你的问题,请参考以下文章

C2内核模块,分配设备号,字符驱动,/设备节点,设备读写,/同步和互斥,ioctl函数,进程休眠,时间和延时,延缓,/proc文件系统,内存分配,数据类型,/内核中断,通过IO内存访问外设

markdown PHPExcel Notes和代码片段

读写锁 与 互斥锁

C++笔记--Linux编程(14)-线程同步

操作系统-同步互斥

同步互斥的实现