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内存访问外设