Linux驱动开发(二)

1.ioctl函数的使用

1.1ioctl的功能

linux内核开发者想要将设备的控制和数据读写分开操作,设备的控制通过

ioctl完成,数据的读写通过read/write函数完成。例如在编写串口启动的时

候,串口需要设置波特率,数据位,停止位,校验位等信息通过ioctl设置

完成,通过read/write函数完成数据的收发工作。

1.2ioctl函数的API

us:  
 #include 
 int ioctl(int fd, unsigned long request, ...);
 功能:设备的控制操作
 参数:
        @fd:文件描述符
  @request:命令码(方向,第三个参数大小)
  @...:可变参数,如果传递传递的是地址  
 返回值:成功返回0,失败返回-1置位错误码    
-------------------------------------------------------------
ks:file_operations: //ioctl的后两个参数原封不动传递给unlocked_ioctl
 long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long arg)
 {
        int ret,which;
     switch(cmd){
         case LED_ON:
                ret = copy_from_user(&which,(void *)arg,4);
                //将which号灯点亮
    break;
   case LED_OFF:
                ret = copy_from_user(&which,(void *)arg,4);
                //将which号灯熄灭
    break;
        }
    }

1.3ioctl命令码的组成

~/linux-5.10.61/Documentation$ vi ./userspace-api/ioctl/ioctl-decoding.rst

Linux驱动开发(二)_第1张图片

Linux驱动开发(二)_第2张图片

自己封装命令码:

#define LED_ON  0b01  00000000000100  01001100 00000001
#define LED_OFF 0b01  00000000000100  01001100 00000000

借助内核的宏封装命令码:

#define LED_ON _IOW('L',1,int)
#define LED_OFF _IOW('L',0,int)

#define _IO(type,nr)		_IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)	_IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

#define _IOC(dir,type,nr,size) \
 (((dir)  << 30) | ((size) << 16))| ((type) << 8) |((nr)   << 0))
  

1.4通过ioctl控制LED亮灭的实例

 先看myled.c里对驱动的详解,按照流程梳理思路,流程已经标好号了

h文件里的东西,随用随定义,根据流程定义使用的东西

myled.c

#include 
#include 
#include 
#include 
#include 
#include "myled.h"
//led驱动
//这些接口需要由字符设备驱动操作,有了字符设备驱动才能操作接口和应用层交互
//驱动三要素写完以后
//驱动上给应用层提供控制硬件逻辑的接口,下控制硬件执行逻辑
//想要和应用层交互,首先要注册字符设备驱动,通过字符设备驱动和应用层交互
//注册字符设备驱动的函数int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
//应用层通过open,read,write,close函数给驱动控制硬件的逻辑
//有了驱动以后,驱动给应用层留接口,接口在struct file_operations中,通过结构体里预留的函数指针指向驱动预留的接口
//应用层里的open,read,write,close和驱动里的myled_open,myled_read myled_write,myled_release交互,从而给驱动操作硬件的逻辑


char kbuf[128]={0};//和用户交互的缓冲区,用来保存用户发来的数据
unsigned int *rcc;//映射的虚拟地址的指针
gpio_t *gpioe,*gpiof;//注意指针类型是gpio_t,不是unsigned int

int myled_open (struct inode *inode, struct file *file){
    //3.完成灯的初始化
    //映射灯的地址,以便控制灯
    if (NULL==(rcc=ioremap(RCC_MP_AHB4ENSETR,4))) {
        printk("ioremap rcc error\n");
        return -ENOMEM;
    }
    //大小不是4了
    if (NULL==(gpioe=ioremap(GPIOE,sizeof(gpio_t)))) {
        printk("ioremap gpioe error\n");
        return -ENOMEM;
    }
    if (NULL==(gpiof=ioremap(GPIOF,sizeof(gpio_t)))) {
        printk("ioremap gpiof error\n");
        return -ENOMEM;
    }

    //初始GPIOE GPIOF化时钟
    *rcc |= (3<<4);
    //初始化仨灯
    //LED1 PE10
    gpioe->MODER &=~(3<<20);//清零
    gpioe->MODER |=(1<<20);//输出
    gpioe->ODR &=~(1<<10);//默认关闭
    //LED2 PF10
    gpiof->MODER &=~(3<<20);//清零
    gpiof->MODER |=(1<<20);//输出
    gpiof->ODR &=~(1<<10);//默认关闭
    //LED3 PE8
    gpioe->MODER &=~(3<<16);//清零
    gpioe->MODER |=(1<<16);//输出
    gpioe->ODR &=~(1<<8);//默认关闭

    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
//不用read,write控制灯,不看
ssize_t myled_read (struct file *file, char __user * ubuf, size_t size, loff_t *offs){
    //将内核的数据搬移到用户空间,读取外设的状态copy_to_user(用户空间地址,内核地址,搬移大小)
    //传的是内核数据的地址
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_to_user(ubuf,kbuf,size); 
    if(ret){
        printk("copy to user error\n");
        return -EIO;
    }
    return size;//成功返回读到的字节数
}
ssize_t myled_write (struct file * file, const char __user *ubuf, size_t size, loff_t *tk_offsets){
    //将控制硬件的代码搬到内核,由内核根据代码控制硬件执行对应逻辑,copy_from_user(内核地址,用户空间地址,搬移大小)
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_from_user(kbuf,ubuf,size);
    if(ret){
        printk("copy from user error\n");
        return -EIO;
    }
    return size;//write成功返回写入的字节数
}
//5.操作底层硬件的代码,操作寄存器led_set_value(哪个灯,状态)
void led_set_value(int ledx,int ledstatus){
    switch (ledx)
    {
    case LED1://PE10
        if(ledstatus==ON){
            gpioe->ODR |=(1<<10);
        }else{
            gpioe->ODR &=~(1<<10);
        }
        break;
    case LED2://PF10
        if(ledstatus==ON){
            gpiof->ODR |=(1<<10);
        }else{
            gpiof->ODR &=~(1<<10);
        }
        break;
    case LED3://PE8
        if(ledstatus==ON){
            gpioe->ODR |=(1<<8);
        }else{
            gpioe->ODR &=~(1<<8);
        }
        break;
    default:
        break;
    }
}
//4.编写驱动接口,承上启下
long myled_ioctl (struct file *file, unsigned int cmd, unsigned long arg){
             //ioctl(fd,              cmd(灯的状态),       ledx(哪个灯))一一对应
    //根据不同的指令执行不同的操作
    int ret,which;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    switch(cmd){
        case LED_ON:
            //将亮灭操作写入内核
            //逻辑没捋明白,先完成应用层
            //copy_from_user(内核地址,用户空间地址,搬移大小)
                //将用户空间的LED1传过来给which,which指向arg的地址(不是&which=&arg),为啥不用&arg
                //因为是将LED1/LED2/LED3的值强转为指针类型给which,也就是&which=(void *)1==>which=1
            ret=copy_from_user(&which,(void *)arg,GET_CMD_SIZE(cmd));
            if(ret){
                printk("copy_from_user error\n");
                return -EIO;
            }
            //控制硬件代码
            led_set_value(which,ON);//为啥不穿arg,which里的值不就是arg吗
                                  //因为得是内核的地址,不能是用户空间的地址?
                                  //
            break;
        case LED_OFF:
            ret=copy_from_user(&which,(void *)arg,GET_CMD_SIZE(cmd));
            if(ret){
                printk("copy_from_user error\n");
                return -EIO;
            }
            //控制硬件代码
            led_set_value(which,OFF);
            break;
    }
    return 0;
}
int myled_release (struct inode *inode, struct file *file){
    iounmap(rcc);
    iounmap(gpioe);
    iounmap(gpiof);
    //printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
//2.1为注册字符设备驱动做准备,填写注册驱动函数的参数
//字符设备驱动名
#define CNAME "myled"
//初始化接口
    const struct file_operations fops={
            .open=myled_open,
            .read=myled_read,
            .write=myled_write,
            .release=myled_release,
            .unlocked_ioctl=myled_ioctl,
    };
//用来接驱动主设备号
int major;
//入口函数
static int __init myled_init(void){
    
    //1.注册驱动
    if(0>(major=register_chrdev(0,CNAME,&fops))){
        printk("register_chdev error\n");
        return -EAGAIN;
    }
    printk("register chrdev success,major = %d\n", major);
    return 0;
}
//出口函数
static void __exit myled_exit(void){
    //2.2注销驱动
    //有注册驱动就有注销驱动,驱动用完了需要注销,不然会一直占着内核资源
    unregister_chrdev(major,CNAME);
}
module_init(myled_init);//入口
module_exit(myled_exit);//出口
MODULE_LICENSE("GPL");//许可证

myled.h

#ifndef _MYLED_H
#define _MYLED_H

typedef struct {
    volatile unsigned int MODER; // 0x00
    volatile unsigned int OTYPER; // 0x04
    volatile unsigned int OSPEEDR; // 0x08
    volatile unsigned int PUPDR; // 0x0C
    volatile unsigned int IDR; // 0x10
    volatile unsigned int ODR; // 0x14
    volatile unsigned int BSRR; // 0x18
    volatile unsigned int LCKR; // 0x1C
    volatile unsigned int AFRL; // 0x20
    volatile unsigned int AFRH; // 0x24
    volatile unsigned int BRR; // 0x28
    volatile unsigned int res;
    volatile unsigned int SECCFGR; // 0x30
} gpio_t;

//灯的状态 用宏函数IOW
#define LED_ON _IOW('L',1,int)
#define LED_OFF _IOW('L',0,int)

//cmd,参数,代表灯的状态
enum led_status{
    OFF,
    ON
};

//哪个灯
typedef enum{
    LED1,
    LED2,
    LED3
}led_t;

#define RCC_MP_AHB4ENSETR 0x50000A28
#define GPIOE 0x50006000
#define GPIOF 0x50007000


#endif /*_MYLED_H*/

test.c

#include "head.h"
#include "myled.h"
#include 
//4.2灯闪烁函数
void led_blink(int fd,int which){
    //if(-1==ioctl(fd,cmd(灯的状态),which(哪个灯)))
    if(-1==ioctl(fd,LED_ON,&which)){
        ERRLOG("ioctl error");
    }
    sleep(1);
    if(-1==ioctl(fd,LED_OFF,&which)){
        ERRLOG("ioctl error");
    }
    sleep(1);
}
int main(int argc, char const *argv[])
{
    int fd;
    if(-1==(fd=(open("/dev/myled",O_RDWR)))){
        ERRLOG("open error");
    }
    
    //ioctl(fd,LED_ON,&LED1)主函数里也不能这么写
    //int ledx=LED1;不想写函数的话,就用这种方法,直接控制灯亮灭
    //ioctl(fd,LED_ON,&ledx);
    while(1){
        //4.1灯闪烁,需要灯闪烁的函数
        led_blink(fd,LED1);
        led_blink(fd,LED2);
        led_blink(fd,LED3);
    }
    return 0;
}

1.5ioctl函数使用练习

  1. 使用ioctl控制LED1-LED3亮灭

  2. 使用ioctl向内核传递char array[128]数组

  3. 使用ioctl写读img_t结构体

    typedef struct{
        int width;
        int high;
    }img_t;
    

myled.h

2.自动创建设备节点

2.1创建设备节点的机制简介

  1. mknod命令
  2. devfs(2.4内核版本引入)
  3. udev/mdev(2.6内核至今)

2.2udev创建设备节点的原理

Linux驱动开发(二)_第3张图片

2.3自动创建设备节点的API

#include 

struct class *class_create(owner, name)
//void class_destroy(struct class *cls)
功能:向上提交目录名
参数:
    @owner:填写THIS_MODULE,给编译器使用
    @name:向上提交的目录的名字
返回值:成功返回结构体指针,失败返回错误码指针

struct device *device_create(struct class *class, struct device *parent,
        dev_t devt, void *drvdata, const char *fmt, ...)
//void device_destroy(struct class *class, dev_t devt)
功能:向上提交创建节点的信息
参数:
    @class:目录的句柄
    @parent:写为NULL
    @devt:设备号
        MKDEV(major,minor) :根据主次设备号合成设备号
  MAJOR(dev) :根据设备号获取主设备号
        MINOR(dev) :根据设备号获取次设备号
    @drvdata:写为NULL
    @fmt,...:创建设备节点的名字  
返回值:成功返回device结构体指针,失败返回错误码指针
        

2.4错误码指针的判断方法

在Linux内核中错误码有4K个,将这4K个错误转换成地址刚好对应的是内核最顶端4K

的位置,内核最顶端4K内存是保留的。可以借助地址区间判断错误码指针,如果将

cls强转为unsigned long类型之后如果大于0xFFFF F000它就是错误,否则就是不是错误。

Linux驱动开发(二)_第4张图片

struct class *cls;
cls = class_create(THIS_MODULE,"hello");
if(IS_ERR(cls)){
    printk("class create error\n");
    return PTR_ERR(cls);
}

2.5自动创建设备节点的实例

myled.h

#ifndef _MYLED_H
#define _MYLED_H

typedef struct {
    volatile unsigned int MODER; // 0x00
    volatile unsigned int OTYPER; // 0x04
    volatile unsigned int OSPEEDR; // 0x08
    volatile unsigned int PUPDR; // 0x0C
    volatile unsigned int IDR; // 0x10
    volatile unsigned int ODR; // 0x14
    volatile unsigned int BSRR; // 0x18
    volatile unsigned int LCKR; // 0x1C
    volatile unsigned int AFRL; // 0x20
    volatile unsigned int AFRH; // 0x24
    volatile unsigned int BRR; // 0x28
    volatile unsigned int res;
    volatile unsigned int SECCFGR; // 0x30
} gpio_t;

//灯的状态 用宏函数IOW
#define LED_ON _IOW('L',1,int)
#define LED_OFF _IOW('L',0,int)
typedef struct{
    int width;
    int high;
}img_t;

#define Array_write _IOW('A',1,char [128])
#define img_read _IOR('I',0,img_t)
#define img_write _IOW('I',1,img_t)
#define img_rw _IOWR('I',1,img_t)
//cmd,参数,代表灯的状态
enum led_status{
    OFF,
    ON
};

//哪个灯
typedef enum{
    LED1=1,
    LED2,
    LED3
}led_t;

#define RCC_MP_AHB4ENSETR 0x50000A28
#define GPIOE 0x50006000
#define GPIOF 0x50007000
#define GET_CMD_SIZE(cmd) ((cmd >> 16)&0x3fff)

#endif /*_MYLED_H*/

myled.c

#include 
#include 
#include 
#include 
#include 
#include 
#include "myled.h"
//led驱动
//这些接口需要由字符设备驱动操作,有了字符设备驱动才能操作接口和应用层交互
//驱动三要素写完以后
//驱动上给应用层提供控制硬件逻辑的接口,下控制硬件执行逻辑
//想要和应用层交互,首先要注册字符设备驱动,通过字符设备驱动和应用层交互
//注册字符设备驱动的函数int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
//应用层通过open,read,write,close函数给驱动控制硬件的逻辑
//有了驱动以后,驱动给应用层留接口,接口在struct file_operations中,通过结构体里预留的函数指针指向驱动预留的接口
//应用层里的open,read,write,close和驱动里的myled_open,myled_read myled_write,myled_release交互,从而给驱动操作硬件的逻辑


char kbuf[128]={0};//和用户交互的缓冲区,用来保存用户发来的数据
unsigned int *rcc;//映射的虚拟地址的指针
gpio_t *gpioe,*gpiof;//注意指针类型是gpio_t,不是unsigned int
struct class *cls;
struct device *dev;

int myled_open (struct inode *inode, struct file *file){
    //3.完成灯的初始化
    //映射灯的地址,以便控制灯
    if (NULL==(rcc=ioremap(RCC_MP_AHB4ENSETR,4))) {
        printk("ioremap rcc error\n");
        return -ENOMEM;
    }
    //大小不是4了
    if (NULL==(gpioe=ioremap(GPIOE,sizeof(gpio_t)))) {
        printk("ioremap gpioe error\n");
        return -ENOMEM;
    }
    if (NULL==(gpiof=ioremap(GPIOF,sizeof(gpio_t)))) {
        printk("ioremap gpiof error\n");
        return -ENOMEM;
    }

    //初始GPIOE GPIOF化时钟
    *rcc |= (3<<4);
    //初始化仨灯
    //LED1 PE10
    gpioe->MODER &=~(3<<20);//清零
    gpioe->MODER |=(1<<20);//输出
    gpioe->ODR &=~(1<<10);//默认关闭
    //LED2 PF10
    gpiof->MODER &=~(3<<20);//清零
    gpiof->MODER |=(1<<20);//输出
    gpiof->ODR &=~(1<<10);//默认关闭
    //LED3 PE8
    gpioe->MODER &=~(3<<16);//清零
    gpioe->MODER |=(1<<16);//输出
    gpioe->ODR &=~(1<<8);//默认关闭

    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
//不用read,write控制灯,不看
ssize_t myled_read (struct file *file, char __user * ubuf, size_t size, loff_t *offs){
    //将内核的数据搬移到用户空间,读取外设的状态copy_to_user(用户空间地址,内核地址,搬移大小)
    //传的是内核数据的地址
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_to_user(ubuf,kbuf,size); 
    if(ret){
        printk("copy to user error\n");
        return -EIO;
    }
    return size;//成功返回读到的字节数
}
ssize_t myled_write (struct file * file, const char __user *ubuf, size_t size, loff_t *tk_offsets){
    //将控制硬件的代码搬到内核,由内核根据代码控制硬件执行对应逻辑,copy_from_user(内核地址,用户空间地址,搬移大小)
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    ret=copy_from_user(kbuf,ubuf,size);
    if(ret){
        printk("copy from user error\n");
        return -EIO;
    }
    return size;//write成功返回写入的字节数
}
//5.操作底层硬件的代码,操作寄存器led_set_value(哪个灯,状态)
void led_set_value(int ledx,int ledstatus){
    switch (ledx)
    {
    case LED1://PE10
        if(ledstatus==ON){
            gpioe->ODR |=(1<<10);
        }else{
            gpioe->ODR &=~(1<<10);
        }
        break;
    case LED2://PF10
        if(ledstatus==ON){
            gpiof->ODR |=(1<<10);
        }else{
            gpiof->ODR &=~(1<<10);
        }
        break;
    case LED3://PE8
        if(ledstatus==ON){
            gpioe->ODR |=(1<<8);
        }else{
            gpioe->ODR &=~(1<<8);
        }
        break;
    default:
        break;
    }
}
//4.编写驱动接口,承上启下
long myled_ioctl (struct file *file, unsigned int cmd, unsigned long arg){
             //ioctl(fd,              cmd(灯的状态),       ledx(哪个灯))一一对应
    //根据不同的指令执行不同的操作
    int ret,which;
    img_t img;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    switch(cmd){
        case LED_ON:
            //将亮灭操作写入内核
            //逻辑没捋明白,先完成应用层
            //copy_from_user(内核地址,用户空间地址,搬移大小)
                //将用户空间的LED1传过来给which,which指向arg的地址(不是&which=&arg),为啥不用&arg
                //因为是将LED1/LED2/LED3的值强转为指针类型给which,也就是&which=(void *)1==>which=1
            ret=copy_from_user(&which,(void *)arg,GET_CMD_SIZE(cmd));
            if(ret){
                printk("copy_from_user error\n");
                return -EIO;
            }
            //控制硬件代码
            led_set_value(which,ON);//为啥不穿arg,which里的值不就是arg吗
                                  //因为得是内核的地址,不能是用户空间的地址
                                  //内核直接调用用户,可能会造成内核崩溃
                                
            break;
        case LED_OFF:
            ret=copy_from_user(&which,(void *)arg,GET_CMD_SIZE(cmd));
            if(ret){
                printk("copy_from_user error\n");
                return -EIO;
            }
            //控制硬件代码
            led_set_value(which,OFF);
            break;
        case Array_write:
            memset(kbuf,0,sizeof(kbuf));
                //用户空间传来的buf,放到内核的kbuf里
            ret=copy_from_user(kbuf,(void *)arg,GET_CMD_SIZE(cmd));
            if(ret){
                printk("copy_from_user error\n");
                return -EIO;
            }
            printk("Array:%s\n",kbuf);
            break;
        case img_read:
                //内核的img传给用户空间的buf
            //memset(&img,0,sizeof(img_t));
            ret=copy_to_user((void *)arg,&img,GET_CMD_SIZE(cmd));
            if(ret){
                printk("copy_to_user error\n");
                return -EIO;
            }
            printk("img_read:%d %d\n",img.high,img.width);
            break;
        case img_write:
            memset(&img,0,sizeof(img_t));
                //用户空间传来的buf,放到内核的img里
            ret=copy_from_user(&img,(void *)arg,GET_CMD_SIZE(cmd));
            if(ret){
                printk("copy_from_user error\n");
                return -EIO;
            }
            printk("img_write:%d %d\n",img.high,img.width);
            break;
        case img_rw:
            memset(&img,0,sizeof(img_t));
                //用户空间传来的buf,放到内核的img里
            ret=copy_from_user(&img,(void *)arg,GET_CMD_SIZE(cmd));
            if(ret){
                printk("copy_from_user error\n");
                return -EIO;
            }
            printk("img_rw:%d %d\n",img.high,img.width);
            ret=copy_to_user((void *)arg,&img,GET_CMD_SIZE(cmd));
            if(ret){
                printk("copy_to_user error\n");
                return -EIO;
            }
            break;
    }
    return 0;
}
int myled_release (struct inode *inode, struct file *file){
    iounmap(rcc);
    iounmap(gpioe);
    iounmap(gpiof);
    //printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
//2.1为注册字符设备驱动做准备,填写注册驱动函数的参数
//字符设备驱动名
#define CNAME "myled"
//初始化接口
    const struct file_operations fops={
            .open=myled_open,
            .read=myled_read,
            .write=myled_write,
            .release=myled_release,
            .unlocked_ioctl=myled_ioctl,
    };
//用来接驱动主设备号
int major;
//入口函数
static int __init myled_init(void){
    
    //1.注册驱动
    if(0>(major=register_chrdev(0,CNAME,&fops))){
        printk("register_chdev error\n");
        return -EAGAIN;
    }
    //创建节点,需要在init中,不能在open中,因为open里要打开的节点还没被创建
    //目录名
    cls=class_create(THIS_MODULE,"hello");
    if(IS_ERR(cls)){
        printk("class_create error");
        return PTR_ERR(cls);
    }
    //节点
    dev=device_create(cls,NULL,MKDEV(major,0),NULL,"myled");
    if(IS_ERR(dev)){
        printk("device_create error");
        return PTR_ERR(dev);
    }
    printk("register chrdev success,major = %d\n", major);
    return 0;
}
//出口函数
static void __exit myled_exit(void){
    //先注销节点,在注销驱动,注意顺序
    class_destroy(cls);
    device_destroy(cls,MKDEV(major,0));
    //2.2注销驱动
    //有注册驱动就有注销驱动,驱动用完了需要注销,不然会一直占着内核资源
    unregister_chrdev(major,CNAME);
}
module_init(myled_init);//入口
module_exit(myled_exit);//出口
MODULE_LICENSE("GPL");//许可证
test.c
#include "head.h"
#include "myled.h"
#include 
//4.2灯闪烁函数
void led_blink(int fd,int which){
    //if(-1==ioctl(fd,cmd(灯的状态),which(哪个灯)))
    if(-1==ioctl(fd,LED_ON,&which)){
        ERRLOG("ioctl error");
    }
    sleep(1);
    if(-1==ioctl(fd,LED_OFF,&which)){
        ERRLOG("ioctl error");
    }
    sleep(1);
}
int main(int argc, char const *argv[])
{
    int fd;
    char Array[128]="hello";
    img_t img={100,200};
    img_t img2={800,900};
    if(-1==(fd=(open("/dev/myled",O_RDWR)))){
        ERRLOG("open error");
    }
    
    //ioctl(fd,LED_ON,&LED1)主函数里也不能这么写
    //int ledx=LED1;不想写函数的话,就用这种方法,直接控制灯亮灭
    //ioctl(fd,LED_ON,&ledx);
    if(-1==ioctl(fd,Array_write,Array)){
        ERRLOG("ioctl error");
    }
    if(-1==ioctl(fd,img_write,&img)){
        ERRLOG("ioctl error");
    }
    memset(&img,0,sizeof(img_t));
    if(-1==ioctl(fd,img_read,&img)){
        ERRLOG("ioctl error");
    }
    

    if(-1==ioctl(fd,img_rw,&img2)){
        ERRLOG("ioctl error");
    }
    printf("test_img:%d %d\n",img.high,img.width);
    printf("test_img:%d %d\n",img2.high,img2.width);
    while(1){
        
        //4.1灯闪烁,需要灯闪烁的函数
        //led_blink(fd,LED1);
        //led_blink(fd,LED2);
        //led_blink(fd,LED3);

    }
    return 0;
}

3.字符设备驱动内部原理

3.1字符设备驱动框架结构

Linux驱动开发(二)_第5张图片

3.2字符设备驱动API

#include 
#include 
#include 

1.cdev结构体
    struct cdev {
        const struct file_operations *ops; //操作方法结构体
        struct list_head list; //构成内核链表
        dev_t dev;  //设备号
        unsigned int count; //设备的个数
    }
2.分配对象
    struct cdev cdev;

 struct cdev *cdev;
 struct cdev *cdev_alloc(void)
 功能:给cdev结构体指针分配内存
    参数:
        @无
 返回值:成功返回结构体指针,失败返回NULL
 
3.对象初始化
 void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    功能:cdev结构体成员的初始化(部分)
    参数:
        @cdev:cdev结构体指针
  @fops:操作方法结构体指针
 返回值:无
4.申请设备号
 int register_chrdev_region(dev_t from, unsigned count, const char *name)
    功能:静态指定设备号
 参数:
        @from:设备号    
        @count:设备的个数  
  @name:名字  cat   /proc/devices
    返回值:成功返回0,失败返回错误码
            
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
   const char *name)
 功能:动态申请设备号
 参数:
        @dev:申请到的设备号     
        @baseminor:次设备号起始值    
        @count:设备的个数     
  @name:名字  cat   /proc/devices
    返回值:成功返回0,失败返回错误码
            
5.注册
 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
 功能:字符设备驱动的注册
 参数:
  @p:cdev结构体指针
  @dev:设备号
  @count:个数
 返回值:成功返回0,失败返回错误码
----------------------------------------------------------------------
6.注销
    void cdev_del(struct cdev *p)
 功能:注销字符设备驱动
    参数:
        @p:cdev的结构体指针
 返回值:无
7.释放设备号
 void unregister_chrdev_region(dev_t from, unsigned count)
 功能:注销设备号
 参数:
        @from:设备号
  @count:设备号个数
 返回值:无
8.释放cdev结构体内存
 void kfree(const void *cdev);
 功能:释放cdev结构体指针内存
 参数:
        @cdev:cdev的首地址
 返回值:无

3.3分步实现字符设备驱动的实例

mycdev.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
//驱动实际上是一个struct cdev结构体
//用户在调用驱动的原理实际上是
//应用层调用open,read,write,close函数,这些都是系统调用
//内核处理系统调用的机制是软中断,此时,linux会从用户模式进入SVC模式,从而进入内核空间
//内核根据储存的汇编文件,寻找对应的系统调用的编号,根据编号找到对应的内核中的函数接口,以open为例,会调用sys_open
//long sys_open(const char __user *filename,int flags,umode_t mode)
//第一个参数就是应用层调open函数时传的带路径的文件名,根据这个文件名可以找到此文件的inode号
//根据inode号找到对应的struct inode结构体,根据这个结构体就可以找到对应的驱动的结构体,如果是字符设备驱动
/*就会找到struct cdev结构体,这个结构体里的const struct file_operations *ops就有我们自己写的mycdev_open,mycdev_read,mycdev_write,mycdev_close函数
//通过自己写的函数操控硬件
struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
} __randomize_layout;*/
//所以要想写驱动,先要建立驱动结构体(分配对象)
//1.分配对象 指针建立需分配内存
struct cdev *mycdev;
#define CNAME "mycdev"
#define COUNT 3
int major=260;
int minor=0;
char kbuf[128]={0};
struct class *cls;
struct device *dev;
//2.1 初始化fops
int mycdev_open (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    return size;
}
int mycdev_release (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
const struct file_operations fops={
    .open=mycdev_open,
    .read=mycdev_read,
    .write=mycdev_write,
    .release=mycdev_release,
};
static int __init mycdev_init(void){
    int ret,i;
    dev_t devno;
    //1.1分配内存空间
    if(NULL==(mycdev=cdev_alloc())){
        printk("cdev_alloc error\n");
        ret=-ENOMEM;//return -ENOMEM;利用ERR1返回错误码
        goto ERR1;
    }
    //2.初始化对象
    cdev_init(mycdev,&fops);//部分初始化
    //3.申请设备号
    //如果传的major>0,就静态指定设备号;
    //major=0,就动态申请设备号
    if(major>0){
        if(0!=(ret=register_chrdev_region(MKDEV(major,minor),COUNT,CNAME))){
            printk("register_chrdev_region error\n");
            //return ret;//这样写并没有释放申请的内存,还需要释放内存
                         //把这一步放到ERR2的后面,这样释放完内存后,会直接执行ERR1
            goto ERR2;
        }
    }else{
        if(0!=(ret=alloc_chrdev_region(&devno,0,COUNT,CNAME))){
            printk("alloc_chrdev_region error\n");
            goto ERR2;
        }
        major=MAJOR(devno);
        minor=MINOR(devno);
    }
    //4.注册
    //创建设备号的方式不同,参数不一样,统一设备号
    
    if(0!=(ret=cdev_add(mycdev,MKDEV(major,minor),COUNT))){
        printk("cdev_add error\n");
        //失败还要释放设备号
        goto ERR3;
    }
    //5.自动创建设备节点也加上
    cls=class_create(THIS_MODULE,CNAME);
    if(IS_ERR(cls)){
        printk("class create error\n");
        ret=PTR_ERR(cls);
        goto ERR4;
    }
    //设备文件不止一个
    for(i=0;i=0;i--){
        device_destroy(cls,MKDEV(major,minor+i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(mycdev);
ERR3:
    unregister_chrdev_region(MKDEV(major,minor),COUNT);
ERR2:
    kfree(mycdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void){
    //4.1释放节点
    int i;
    for(i=0;i
test.c
#include 
typedef struct{
    int width;
    int high;
}img_t;
int main(int argc, char const *argv[])
{
    int fd;

    unsigned int unum=10;
    img_t uimg={.high=100,
                .width=200};
    char buf[128]="快去看封神!!!";
    if (-1==(fd=open("/dev/mycdev0",O_RDWR))){
        ERRLOG("open error");
    }
   
    write(fd,buf,sizeof(buf));
    memset(buf,0,sizeof(buf));
    read(fd,buf,sizeof(buf));
    printf("buf = %s\n",buf);

    
    write(fd,&unum,sizeof(unum));
    unum=0;
    read(fd,&unum,sizeof(unsigned int));
    printf("num=%d\n",unum);
  
    write(fd,&uimg,sizeof(uimg));
    memset(&uimg,0,sizeof(uimg));
    read(fd,&uimg,sizeof(uimg));
    printf("uimg=%d %d\n",uimg.high,uimg.width);
    
    close(fd);
    return 0;
}

4.作业

  1. 根据设备文件识别设备,并分别控制LED的亮灭
1.操作LED亮灭的方法:
 echo 1 > /dev/mycdev0 LED1亮 (echo = open write close)
 echo 0 > /dev/mycdev0 LED1灭 (echo = open write close)
 echo 1 > /dev/mycdev1 LED2亮 (echo = open write close)
 echo 0 > /dev/mycdev1 LED2灭 (echo = open write close)
 echo 1 > /dev/mycdev2 LED3亮 (echo = open write close)
 echo 0 > /dev/mycdev2 LED3灭 (echo = open write close)

2.框架结构
    user:/dev/mycdev0  /dev/mycdev1  /dev/mycdev2
         260,0        260,1         260,2
    --------------------------------------------------------
    kernel:	驱动
         inode0          inode1         inode2
         int mycdev_open(struct inode* inode, struct file* file)
     {
      MINOR(inode->i_rdev);
     }
    --------------------------------------------------------
            LED1        LED2           LED3

myled.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "myled.h"

//1.分配对象
#define CNAME "myled"
#define COUNT 3
struct cdev *myled;
//struct inode *inode;//用于获取设备号给应用层,实现一个应用程序一个驱动控制三个设备节点
struct class *cls;
struct device *dev;
int major=260;
int minor=0;
int curminor;//用来接次设备号
char kbuf[128]={0};

//6.硬件部分
//6.1创建映射的虚拟地址指针
unsigned int *rcc;
gpio_t *gpioe,*gpiof;
//2.1fops

int myled_open (struct inode *inode, struct file *file){
    curminor=MINOR(inode->i_rdev);
    //6.2完成灯的映射
    if (NULL==(rcc=ioremap(RCC_MP_AHB4ENSETR,4))) {
        printk("ioremap rcc error\n");
        return -ENOMEM;
    }
    //大小不是4了
    if (NULL==(gpioe=ioremap(GPIOE,sizeof(gpio_t)))) {
        printk("ioremap gpioe error\n");
        return -ENOMEM;
    }
    if (NULL==(gpiof=ioremap(GPIOF,sizeof(gpio_t)))) {
        printk("ioremap gpiof error\n");
        return -ENOMEM;
    }

    //初始GPIOE GPIOF化时钟
    *rcc |= (3<<4);
    //初始化仨灯
    //LED1 PE10
    gpioe->MODER &=~(3<<20);//清零
    gpioe->MODER |=(1<<20);//输出
    gpioe->ODR &=~(1<<10);//默认关闭
    //LED2 PF10
    gpiof->MODER &=~(3<<20);//清零
    gpiof->MODER |=(1<<20);//输出
    gpiof->ODR &=~(1<<10);//默认关闭
    //LED3 PE8
    gpioe->MODER &=~(3<<16);//清零
    gpioe->MODER |=(1<<16);//输出
    gpioe->ODR &=~(1<<8);//默认关闭
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t myled_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}
ssize_t myled_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    //判断是哪个设备文件
    switch(curminor){
        case 0://PE10
            if(kbuf[0]=='1'){//注意这里是字符1,不是整型1
                printk("LED1 ON\n");
                gpioe->ODR |=(1<<10);
            }else{
                printk("LED1 OFF\n");
                gpioe->ODR &=~(1<<10);
            }
            break;
        case 1://PF10
            if(kbuf[0]=='1'){
                printk("LED2 ON\n");
                gpiof->ODR |=(1<<10);
            }else{
                printk("LED2 OFF\n");
                gpiof->ODR &=~(1<<10);
            }
            break;
        case 2:
            if(kbuf[0]=='1'){
                printk("LED3 ON\n");
                gpioe->ODR |=(1<<8);
            }else{
                printk("LED3 OFF\n");
                gpioe->ODR &=~(1<<8);
            }
            break;
    }
    
    return size;
}
int myled_close (struct inode *inode, struct file *file){
    iounmap(rcc);
    iounmap(gpioe);
    iounmap(gpiof);
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
const struct file_operations fops={
    .open=myled_open,
    .read=myled_read,
    .write=myled_write,
    .release=myled_close,
};
static int __init myled_init(void){
    int ret,i;
    dev_t devno;
    //1.1分配空间
    if(NULL==(myled=cdev_alloc())){
        printk("cdev_alloc error\n");
        ret=-ENOMEM;
        goto ERR1;
    }
    //2.初始化对象
    cdev_init(myled,&fops);
    //3.申请设备号
    if(major>0){
        if(0!=(ret=register_chrdev_region(MKDEV(major,minor),COUNT,CNAME))){
            printk("register_chrdev_region error\n");
            goto ERR2;//释放申请的空间
        }
    }else{
        if(0!=(ret=alloc_chrdev_region(&devno,0,COUNT,CNAME))){
            printk("alloc_chrdev_region error\n");
            goto ERR2;//释放申请的空间
        }
        //统一设备号
        major=MAJOR(devno);
        minor=MINOR(devno);
    }
    //4.注册
    if(0!=(ret=cdev_add(myled,MKDEV(major,minor),COUNT))){
        printk("cdev_add error\n");
        goto ERR3;//释放设备号
    }
    //5.自动创建设备节点
    cls=class_create(THIS_MODULE,CNAME);
    if(IS_ERR(cls)){
        printk("class_create error\n");
        ret=PTR_ERR(cls);
        goto ERR4;//注销
    }
    for(i=0;i=0;i--){
        device_destroy(cls,MKDEV(major,minor+i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(myled);
ERR3:
    unregister_chrdev_region(MKDEV(major,minor),3);
ERR2:
    kfree(myled);
ERR1:
    return ret;
}
static void __exit myled_exit(void){
    //4.1释放节点
    int i;
    for(i=0;i

myled.h

#ifndef _MYLED_H
#define _MYLED_H

typedef struct {
    volatile unsigned int MODER; // 0x00
    volatile unsigned int OTYPER; // 0x04
    volatile unsigned int OSPEEDR; // 0x08
    volatile unsigned int PUPDR; // 0x0C
    volatile unsigned int IDR; // 0x10
    volatile unsigned int ODR; // 0x14
    volatile unsigned int BSRR; // 0x18
    volatile unsigned int LCKR; // 0x1C
    volatile unsigned int AFRL; // 0x20
    volatile unsigned int AFRH; // 0x24
    volatile unsigned int BRR; // 0x28
    volatile unsigned int res;
    volatile unsigned int SECCFGR; // 0x30
} gpio_t;

//灯的状态 用宏函数IOW
#define LED_ON _IOW('L',1,int)
#define LED_OFF _IOW('L',0,int)
typedef struct{
    int width;
    int high;
}img_t;

#define Array_write _IOW('A',1,char [128])
#define img_read _IOR('I',0,img_t)
#define img_write _IOW('I',1,img_t)
#define img_rw _IOWR('I',1,img_t)
//cmd,参数,代表灯的状态
enum led_status{
    OFF,
    ON
};

//哪个灯
typedef enum{
    LED1=1,
    LED2,
    LED3
}led_t;

#define RCC_MP_AHB4ENSETR 0x50000A28
#define GPIOE 0x50006000
#define GPIOF 0x50007000
#define GET_CMD_SIZE(cmd) ((cmd >> 16)&0x3fff)

#endif /*_MYLED_H*/

5.file结构体的功能

5.1file结构体的作用

在使用系统调用打开文件的时候会返回fd,fd就是文件描述符,它是一个

整数值,它又要代表打开的文件及打开文件的方式(只读,只写,读写等),

这些打开文件的信息并不是通过整数值表示的而是通过file的结构体表示的。

fd和file结构体的关系?

每一个进程的fd都是独立的,如果想要直到fd和file的关系就可以取进程的结构体内去查找。

struct task_struct {
    volatile long state; //进程的状态(运行态R,休眠(D|S),停止态(T),僵尸态(Z),死亡态(X))
 int on_cpu;  //进程运行在那个核上(核的编号)
    int prio;    //进程的优先级(100-139)
    unsigned int rt_priority; //实时进程的优先级(0-99)
    pid_t  pid; //进程的PID
    struct task_struct  *real_parent; //创建当前进程的父进程
 struct task_struct  *parent; //当前的父进程
 struct list_head children; //子进程
 struct list_head sibling;  //兄弟
 struct task_struct *group_leader; //组长进程
 struct signal_struct *signal; //进程相关信号(捕捉,忽略,默认)
    struct thread_struct thread; //进程内的线程
 struct files_struct *files;   /* Open file information: */
};

struct files_struct {
 struct file  * fd_array[NR_OPEN_DEFAULT];
};

struct file {
    struct path     f_path;  //路径相关
    unsigned int    f_flags; 
    //open的第二个参数,就是打开文件的方式
    const struct file_operations    *f_op; //操作方法结构体
    fmode_t         f_mode;  //文件的权限
    loff_t          f_pos;   //光标
    void            *private_data; //私有数据
}

Linux驱动开发(二)_第6张图片

前面说过了,fd一个整数无法既表示打开的文件又表示打开文件的方式,所以实际上文件信息是通过file结构体表示的。而通过open函数得到的整数实际上是fd_array数组中的下标,而这个下标就指向了struct file结构体。

5.2如何通过fd调用驱动

fd-->fd_array[fd]-->file-->f_op-->(open read write release)函数指针

-->mycdev_open mycdev_read mycdev_write mycdev_close

Linux驱动开发(二)_第7张图片

        fd是open函数的返回值,当应用层调用open时,软中断SVC模式进入内核空间,内核空间根据汇编文件里的表找到对应的sys_open函数,sys_open函数中filename的参数就是应用层open传过来的带路径的文件名,根据文件名(ls -i)可以找到文件对应的设备号,根据设备号可以找到存储设备信息的struct inode结构体,在调用open时,这和个结构体会创建struct file结构体,此结构体存放文件的信息,应用层open产生的fd在fd_array[]数组中,根据fd找到数组中的位置,可以找到struct inod创建的struct file结构体,这个结构体中存放的f_op的指针就指向了驱动结构体struct cdev,实际上inode结构体访问驱动,也是通过file结构体中的f_op指针,而不是直接指向,struct cdev中的ops指针就可以访问自己写的设备驱动函数(myled_open,myled_read,myled_write,myled_close)从而控制硬件。

5.3file结构体私有数据传参

int mycdev_open(struct inode* inode, struct file* file)
{
    int curminor;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    curminor = MINOR(inode->i_rdev);
    file->private_data = (void *)curminor;
    return 0;
}

ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    int curminor = (int)file->private_data;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    switch (curminor) {
    case 0:
        kbuf[0] == '1' ? (printk("LED1 ON\n")) : (printk("LED1 OFF\n"));
        break;
    case 1:
        kbuf[0] == '1' ? (printk("LED2 ON\n")) : (printk("LED2 OFF\n"));
        break;
    case 2:
        kbuf[0] == '1' ? (printk("LED3 ON\n")) : (printk("LED3 OFF\n"));
        break;
    }
    return size;
}

6.字符设备驱动设备号管理

设备号在Linux内核中通过HASH表管理,在存的时候采用主设备号%255

静态指定设备号:主设备号的最大值是511,主设备号的区间是[0-511]

动态申请设备号:主设备号范围[511-384]和[254-234]

Linux驱动开发(二)_第8张图片

7.Linux内核中并发和竞态的解决方法

7.1竞态产生的原因

当多个进程访问同一个驱动的临界资源的时候,竞态就会产生了。

Linux驱动开发(二)_第9张图片

7.2竞态产生的根本原因

  1. 对于单核处理器来说,如果内核支持抢占就会产生竞态
  2. 对于多核处理器(smp)来说,核与核之间本身就是并行执行,也会产生竞态
  3. 中断和进程间也会产生竞态
  4. 在ARM架构下,中断和中断间会产生竞态   (中断不支持嵌套)

7.3解决竞态的方法  

  1. 中断屏蔽(了解)
  2. 自旋锁(重点)
  3. 信号量(重点)
  4. 互斥体(会用)
  5. 原子操作(会用)

7.4中断屏蔽

中断屏蔽:只针对单核有效,中断屏蔽就是临时将中断关闭掉。

中断屏蔽保护的临界区比较小,在临界区内不能使用耗时,延时,

甚至休眠的操作。否则中断屏蔽的时间比较长就导致用户数据的

丢失或者内核的崩溃。

local_irq_disable(); //屏蔽中断
//临界区
local_irq_enable();  //开启中断

7.5自旋锁

7.5.1什么是自旋锁

自旋锁:当一个进程获取到自旋锁之后,如果此时另外一个进程也想获取

这把锁,后一个进程就处于自旋状态,自旋锁又叫忙等锁。

自旋锁的临界区要尽可能小

7.5.2自旋锁的特点

  1. 自旋锁本身就是真多核设计的

  2. 自旋状态是需要消耗cpu资源的

  3. 自旋锁保护的临界区比较小,不能够在自旋锁内核使用延时,耗时,休眠等操作

    在自旋锁内核不能够调用copy_to_user、copy_from_user等类似的函数。

  4. 自旋锁可以工作在中断上下文

  5. 自旋锁在上锁前会关闭抢占

  6. 自旋锁可能会产生死锁(例如在同一个进程能多次获取同一把未解锁的锁)

7.5.3自旋锁的API

1.定义自旋锁
    spinlock_t lock;
2.初始化自旋锁
    void spin_lock_init(spinlock_t *lock)
3.上锁
    void spin_lock(spinlock_t *lock)
4.解锁
    void spin_unlock(spinlock_t *lock)

7.5.4自旋锁的实例

mycdev.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//1.分配对象 指针建立需分配内存
struct cdev *mycdev;
#define CNAME "mycdev"
#define COUNT 3
int major=260;
int minor=0;
char kbuf[128]={0};
struct class *cls;
struct device *dev;
//i.定义自旋锁
spinlock_t lock;
int flag=0;
//2.1 初始化fops
int mycdev_open (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    //iii.上锁
    spin_lock(&lock);
    if(flag!=0){
        spin_unlock(&lock);
        return -EBUSY;
    }
    flag=1;//这样写,临界区只有这一行,不会涉及到不允许使用的函数,同时flag的值也能使其他进程不能对此驱动二次加锁
    spin_unlock(&lock);
    return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    return size;
}
int mycdev_release (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    spin_lock(&lock);
    flag=0;
    spin_unlock(&lock);//这块的上锁解锁过程完全可以不写,但一般会写
    return 0;
}
const struct file_operations fops={
    .open=mycdev_open,
    .read=mycdev_read,
    .write=mycdev_write,
    .release=mycdev_release,
};
static int __init mycdev_init(void){
    int ret,i;
    dev_t devno;
    //ii.初始化锁,须在init里完成,不然两个进程可以同时打开一个驱动文件
    spin_lock_init(&lock);
    //1.1分配内存空间
    if(NULL==(mycdev=cdev_alloc())){
        printk("cdev_alloc error\n");
        ret=-ENOMEM;//return -ENOMEM;利用ERR1返回错误码
        goto ERR1;
    }
    //2.初始化对象
    cdev_init(mycdev,&fops);//部分初始化
    //3.申请设备号
    //如果传的major>0,就静态指定设备号;
    //major=0,就动态申请设备号
    if(major>0){
        if(0!=(ret=register_chrdev_region(MKDEV(major,minor),COUNT,CNAME))){
            printk("register_chrdev_region error\n");
            //return ret;//这样写并没有释放申请的内存,还需要释放内存
                         //把这一步放到ERR2的后面,这样释放完内存后,会直接执行ERR1
            goto ERR2;
        }
    }else{
        if(0!=(ret=alloc_chrdev_region(&devno,0,COUNT,CNAME))){
            printk("alloc_chrdev_region error\n");
            goto ERR2;
        }
        major=MAJOR(devno);
        minor=MINOR(devno);
    }
    //4.注册
    //创建设备号的方式不同,参数不一样,统一设备号
    
    if(0!=(ret=cdev_add(mycdev,MKDEV(major,minor),COUNT))){
        printk("cdev_add error\n");
        //失败还要释放设备号
        goto ERR3;
    }
    //5.自动创建设备节点也加上
    cls=class_create(THIS_MODULE,CNAME);
    if(IS_ERR(cls)){
        printk("class create error\n");
        ret=PTR_ERR(cls);
        goto ERR4;
    }
    //设备文件不止一个
    for(i=0;i=0;i--){
        device_destroy(cls,MKDEV(major,minor+i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(mycdev);
ERR3:
    unregister_chrdev_region(MKDEV(major,minor),COUNT);
ERR2:
    kfree(mycdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void){
    //4.1释放节点
    int i;
    for(i=0;i
  1. test.c
#include 

int main(int argc, const char* argv[])
{
    int fd;
    char buf[128] = "i am test data uaccess...";
    if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
        ERRLOG("open error");

    write(fd, buf, sizeof(buf));

    sleep(5);
    memset(buf,0,sizeof(buf));
    read(fd, buf, sizeof(buf));
    printf("buf = %s\n",buf);

    close(fd);
    return 0;
}

7.6信号量

7.6.1什么是信号量

信号量:当一个进程获取到信号量之后,如果此时另外一个进程也想获取这个

信号量,后一个进程处于休眠状态。

信号量的临界区要大效率才高

7.6.2信号量的特点

  1. 对多核是有效的
  2. 在获取不到信号量的时候进程处于休眠状态(不消耗cpu资源)
  3. 信号量保护的临界区比较大,在信号量保护的临界区中就可以执行延时,耗时,甚至休眠的操作
  4. 信号量工作在进程上下文
  5. 信号量也不会导致死锁
  6. 信号量不会关闭抢占

7.6.3信号量的API

1.定义信号量
 struct semaphore sem;
2.初始化信号 
    void sema_init(struct semaphore *sem, int val)
    //val一般初始化为1,表示只有一个进程能获取到资源,其他的进程获取不到休眠
3.获取信号量
    void down(struct semaphore *sem) //让信号量的计数值减去1
    int down_trylock(struct semaphore *sem);
    //不会休眠,尝试获取信号量,如果获取成功返回0,如果获取不成功返回1
4.释放信号量
    void up(struct semaphore *sem); //释放信号量,让值加1

7.6.4信号量的使用实例

mycdev.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define COUNT 3
#define CNAME "mycdev"

struct cdev* cdev;
int major = 0;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
struct semaphore sem; //定义信号量

int mycdev_open(struct inode* inode, struct file* file)
{
    if(down_trylock(&sem)){
        return -EBUSY;
    }
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}

ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    up(&sem);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev_alloc error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.cdev成员初始化
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major > 0) {
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
    } else {
        ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("cdev_add error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < COUNT; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i + minor), NULL, "%s%d", CNAME, i);
        if (IS_ERR(dev)) {
            printk("device_create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }
    // 初始化信号量
    sema_init(&sem,1);

    return 0; /*********************别忘记写!!************************/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < COUNT; i++) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

7.7互斥体

7.7.1什么是互斥体

互斥体:当一个进程获取到互斥体之后,如果此时另外一个进程也想获取这个

互斥体,后一个进程处于休眠状态。互斥体又叫排它锁。

7.7.2互斥体的特点

  1. 对多核是有效的

  2. 在获取不到互斥体的时候进程处于休眠状态(不消耗cpu资源)

  3. 互斥体保护的临界区比较大,在互斥体保护的临界区中就可以执行延时,耗时,甚至休眠的操作

  4. 互斥体工作在进程上下文

  5. 互斥体也不会导致死锁

  6. 互斥体不会关闭抢占

  7. 互斥体在获取不到资源的时候不会立即进入休眠状态,会适当的等一会在进入休眠状态

    如果在等的期间解锁了,那它不会休眠的直接运行。(在进程上下文中,在不确定临界

    区大小的时候,优先选择互斥体)

7.7.3互斥体的API

1.定义互斥体
    struct mutex mutex;
2.初始化互斥体
    mutex_init(&mutex);
3.获取互斥体
    void  mutex_lock(struct mutex *lock)
    int  mutex_trylock(struct mutex *lock)
    //尝试获取互斥体,获取到返回1,获取不到返回0
4.释放互斥体
    void  mutex_unlock(struct mutex *lock)

7.7.4互斥体的使用实例

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define COUNT 3
#define CNAME "mycdev"

struct cdev* cdev;
int major = 0;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
struct mutex mutex;

int mycdev_open(struct inode* inode, struct file* file)
{
    if(!mutex_trylock(&mutex)){
        return -EBUSY;
    }
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}

ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    mutex_unlock(&mutex);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev_alloc error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.cdev成员初始化
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major > 0) {
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
    } else {
        ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("cdev_add error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < COUNT; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i + minor), NULL, "%s%d", CNAME, i);
        if (IS_ERR(dev)) {
            printk("device_create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }
    // 初始化互斥体
    mutex_init(&mutex);

    return 0; /*********************别忘记写!!************************/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < COUNT; i++) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

7.8原子操作

7.8.1原子操作的原理

原子操作:系统系统“原子”这个词来名字当前的操作,含义是把真个操作看成一个不可被分割的整体。

在对原子变量操作的时候内核会保证只有一个处理器才操作这个变量,对原子变量的值修改过程内核

是通过内联汇编完成的。

typedef struct {
 int counter;
} atomic_t;

7.8.2原子操作的API

方式1: 
 1.定义并初始化原子变量
        atomic_t atm = ATOMIC_INIT(1);
    2.获取原子变量
        int atomic_dec_and_test(atomic_t *v)
        将原子变量的值减去1,然后判断是否是0,如果是0表示获取锁成功,成功返回
        真,否则获取锁失败,失败返回假
    3.释放原子变量
        atomic_inc(atomic_t *v)
        让原子变量的值加1
方式2: 
 1.定义并初始化原子变量
        atomic_t atm = ATOMIC_INIT(-1);
    2.获取原子变量
        int atomic_inc_and_test(atomic_t *v)
        将原子变量的值加上1,然后判断是否是0,如果是0表示获取锁成功,成功返回
        真,否则获取锁失败,失败返回假
    3.释放原子变量
        atomic_dec(atomic_t *v)
        让原子变量的值减1

7.8.3原子操作的实例

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define COUNT 3
#define CNAME "mycdev"

struct cdev* cdev;
int major = 0;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
atomic_t atm = ATOMIC_INIT(1);
int mycdev_open(struct inode* inode, struct file* file)
{
    if(!atomic_dec_and_test(&atm)){
        atomic_inc(&atm);
        return -EBUSY;
    }
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}

ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    atomic_inc(&atm);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev_alloc error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.cdev成员初始化
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major > 0) {
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
    } else {
        ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("cdev_add error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < COUNT; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i + minor), NULL, "%s%d", CNAME, i);
        if (IS_ERR(dev)) {
            printk("device_create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }

    return 0; /*********************别忘记写!!************************/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < COUNT; i++) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

8.IO模型

8.1IO模型的种类

  1. 非阻塞IO
  2. 阻塞IO
  3. IO多路复用
  4. 异步通知(信号驱动IO)

8.2非阻塞IO模型

非阻塞IO模型:在使用非阻塞方式打开设备文件的时候,使用read函数

想要从底层硬件读取数据,此时不管底层的数据是否准备好,read函数

都要立即返回(数据准备好返回数据,没准备好数据返回错误码)。

US:
 fd = open("/dev/mycdev0",O_RDWR|O_NONBLOCK); //非阻塞打开设备文件
 read(fd,buf,sizeof(buf));   //不管数据是否准备好都要立即返回
-----------------------------------------------
KS:mycdev_read(file,ubuf,size,offs)
 {
  if(file->f_flags & O_NONBLOCK){
         //非阻塞方式打开
         //从底层硬件将数据先读取到内核空间,调用copy_to_user拷贝到用户空间
            //在读取底层硬件数据的时候,数据没准备好,将返回错误码
        }
 }

笔试题:如果使用阻塞方式打开文件之后,在保证不关闭这个文件,将它改为非阻塞?

答:使用如下两条语句设置它

​ unsigned int flags = fcntl(fd,F_GETFL);

​ fcntl(fd,F_SETFL,flags|O_NONBLOCK);

8.3阻塞IO模型

8.3.1什么是阻塞IO模型

阻塞IO模型:当使用open以阻塞方式打开设备文件的时候,使用read从驱动中

读取数据的时候,如果数据没有准备好让进程休眠。如果硬件的数据准备好之后

硬件就会产生中断,在中断处理函数中唤醒这个休眠的进程。进程被唤醒之后从

底层硬件中将数据读取到内核空间,然后再将内核空间的数据拷贝到用户空间。

8.3.2阻塞IO模型的实现

US:
 fd = open("/dev/mycdev0",O_RDWR); //阻塞打开设备文件
 read(fd,buf,sizeof(buf));   //如果数据准备好立即读取数据,如果数据没有准备阻塞等待
-----------------------------------------------
KS:mycdev_read(file,ubuf,size,offs)
 {
     //1.判断打开方式
  if(file->f_flags & O_NONBLOCK){
         //非阻塞方式打开
            return -EINVAL;
        }else{
            //阻塞方式打开,判断数据准备准备好,如果没有准备好让进程休眠
            wait_event(wq_head,condition);
            wait_event_interruptible(wq_head, condition);
        }
     
     //从硬件中将数据读取到内核空间
     
     //调用copy_to_user将数据拷贝到用户空间
 }

 中断处理函数:
        condition=1;
        wake_up(&wq_head);//唤醒休眠的进程
        wake_up_interruptible(&wq_head);//唤醒休眠的进程

8.3.3阻塞IO模型的API

1.定义等待队列头
    wait_queue_head_t wq_head;
2.初始化等待队列头
    init_waitqueue_head(&wq_head);
3.阻塞
    wait_event(wq_head,condition);
 功能:让进程进入到不可中断的休眠态
 参数:
        @wq_head:等待队列头
        @condition:数据是否准备好的条件
 返回值:无
    wait_event_interruptible(wq_head, condition);
 功能:让进程进入到可中断的休眠态
 参数:
        @wq_head:等待队列头
        @condition:数据是否准备好的条件
 返回值:数据准备好返回0,如果是信号唤醒的休眠返回-ERESTARTSYS
4.唤醒
    condition=1;
    wake_up(&wq_head);//唤醒休眠的进程
    wake_up_interruptible(&wq_head);//唤醒休眠的进程        

8.3.4阻塞IO模型的实例

Linux驱动开发(二)_第10张图片

mycdev.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define COUNT 3
#define CNAME "mycdev"
struct cdev* cdev;
int major = 0;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
wait_queue_head_t wq_head; // 定义等待队列头
unsigned int condition = 0; // 默认数据没准备好
int mycdev_open(struct inode* inode, struct file* file)
{

    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    // 判断是否是阻塞打开的
    if (file->f_flags & O_NONBLOCK) {
        return -EINVAL;
    } else {
        // 阻塞
        ret = wait_event_interruptible(wq_head, condition);
        if (ret) {
            printk("wait interrupt by signal...\n");
            return ret;
        }
    }
    // 将数据拷贝到用户空间
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    // 将condition清零
    condition = 0;
    return size;
}

ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    // 唤醒
    condition = 1;
    wake_up_interruptible(&wq_head);
    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev_alloc error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.cdev成员初始化
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major > 0) {
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
    } else {
        ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("cdev_add error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < COUNT; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i + minor), NULL, "%s%d", CNAME, i);
        if (IS_ERR(dev)) {
            printk("device_create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }

    init_waitqueue_head(&wq_head); // 初始化等待队列头
    return 0; /*********************别忘记写!!************************/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < COUNT; i++) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include 

int main(int argc, const char* argv[])
{
    int fd;
    pid_t pid;
    char buf[128] = "i am test block io code...";
    if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
        PRINT_ERR("open error");

    pid = fork();
    if (pid == -1) {
        PRINT_ERR("fork error");
    } else if (pid == 0) {
        // 子进程读
        while(1){
            memset(buf,0,sizeof(buf));
            read(fd,buf,sizeof(buf));
            printf("buf = %s\n",buf);
        }
    } else {
        // 父进程写
        while(1){
            sleep(3);
            write(fd,buf,sizeof(buf));
        }
    }

8.4IO多路复用IO模型

8.4.1IO多路复用介绍

在同一个APP应用程序中如果想要同时监听多个文件描述符对应的数据的时候

就要使用IO多路复用IO模型。将被监听的文件描述符放到文件描述符的集合中,

让后时候select/poll/epoll监听它们,如果所有的文件描述符数据都没有准备好

进程休眠。如果有一个或者多个硬件数据准备好了,就会产生硬件中断,在中断

处理函数中唤醒休眠的进程。此时select/poll/epoll就返回了,然后调用read从

就绪的文件描述中将数据读取到用户空间即可。

8.4.2IO多路复用IO模型的实现

Linux驱动开发(二)_第11张图片

US:
 int fd1,fd2;
 
 fd1 = open("/dev/mycdev0",O_RDWR);
 fd2 = open("/dev/input/mouse0",O_RDWR);

 fd_set rfds; //定义表
 while(1){
        FD_ZERO(&rfds); //将读表清空
        FD_SET(fd1,&rfds); //将fd1文件描述符放到读表中
        FD_SET(fd2,&rfds); //将fd2文件描述符放到读表中

        select(fd2+1,&rfds,NULL,NULL,NULL);

        if(FD_ISSET(fd1,&rfds)){
            memset(buf1,0,sizeof(buf1));
            read(fd1,buf1,sizeof(buf1));
            printf("buf1 = %s\n",buf1);
        }
        if(FD_ISSET(fd2,&rfds)){
            memset(buf2,0,sizeof(buf2));
            read(fd2,buf2,sizeof(buf2));
            printf("buf2 = %s\n",buf2);
        }
    }
-------------------------------------------------------------
KS:fops: //应用层select/poll/epoll都是调用驱动的poll
 __poll_t (*poll) (struct file *file, struct poll_table_struct *wait)
    {
     //1.定义返回值的变量
        __poll_t mask=0;
        //2.调用poll_wait函数
        poll_wait(file,等待队列头,wait);
        //3.根据数据是否准备好,来决定是否置位mask
        if(condition){
         mask |= EPOLLIN;  //EPOLLIN可读 EPOLLOUT可写
        }
        //4.返回mask
        return mask;
    }
 
 grep ".poll =" * -nR

8.4.3IO多路复用实例

mycdev.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//还没讲中断,所以采取,A进程读取数据,B进程通过写入数据唤醒A进程
//1.分配对象 指针建立需分配内存
struct cdev *mycdev;
#define CNAME "mycdev"
#define COUNT 3
int major=260;
int minor=0;
char kbuf[128]={0};
struct class *cls;
struct device *dev;
//ii.i定义等待队列头
wait_queue_head_t wq_head;
//ii.ii 定义条件
unsigned int condition;
//2.1 初始化fops
int mycdev_open (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *offs){
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    //将数据拷贝到用户空间
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    //将条件清零,不然下次条件没发生也能直接读取了
    condition=0;
    return size;
}
ssize_t mycdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offs){
    int ret;
    //iv.唤醒进程
    condition=1;
    wake_up_interruptible(&wq_head);
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    return size;
}
__poll_t mycdev_poll(struct file *file, struct poll_table_struct *wait){
    //i.定义返回值变量
    __poll_t mask=0;
    //ii.调用poll_wait
    poll_wait(file,&wq_head,wait);
    //iii.数据准备好,置为mask
    if(condition){
        mask|=EPOLLIN;
    }
    return mask;
}
int mycdev_release (struct inode *inode, struct file *file){
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
const struct file_operations fops={
    .open=mycdev_open,
    .read=mycdev_read,
    .write=mycdev_write,
    .poll=mycdev_poll,
    .release=mycdev_release,
};
static int __init mycdev_init(void){
    int ret,i;
    dev_t devno;
    //1.1分配内存空间
    if(NULL==(mycdev=cdev_alloc())){
        printk("cdev_alloc error\n");
        ret=-ENOMEM;//return -ENOMEM;利用ERR1返回错误码
        goto ERR1;
    }
    //2.初始化对象
    cdev_init(mycdev,&fops);//部分初始化
    //3.申请设备号
    //如果传的major>0,就静态指定设备号;
    //major=0,就动态申请设备号
    if(major>0){
        if(0!=(ret=register_chrdev_region(MKDEV(major,minor),COUNT,CNAME))){
            printk("register_chrdev_region error\n");
            //return ret;//这样写并没有释放申请的内存,还需要释放内存
                         //把这一步放到ERR2的后面,这样释放完内存后,会直接执行ERR1
            goto ERR2;
        }
    }else{
        if(0!=(ret=alloc_chrdev_region(&devno,0,COUNT,CNAME))){
            printk("alloc_chrdev_region error\n");
            goto ERR2;
        }
        major=MAJOR(devno);
        minor=MINOR(devno);
    }
    //4.注册
    //创建设备号的方式不同,参数不一样,统一设备号
    
    if(0!=(ret=cdev_add(mycdev,MKDEV(major,minor),COUNT))){
        printk("cdev_add error\n");
        //失败还要释放设备号
        goto ERR3;
    }
    //5.自动创建设备节点也加上
    cls=class_create(THIS_MODULE,CNAME);
    if(IS_ERR(cls)){
        printk("class create error\n");
        ret=PTR_ERR(cls);
        goto ERR4;
    }
    //设备文件不止一个
    for(i=0;i=0;i--){
        device_destroy(cls,MKDEV(major,minor+i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(mycdev);
ERR3:
    unregister_chrdev_region(MKDEV(major,minor),COUNT);
ERR2:
    kfree(mycdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void){
    //4.1释放节点
    int i;
    for(i=0;i

test.c

#include "head.h"
int main(int argc, char const *argv[])
{
    int fd1,fd2,i;
    char buf[128]={0};
    //1.构建文件描述
    if(-1==(fd1=open("/dev/mycdev0",O_RDWR))){
        ERRLOG("fd1 open error");
    }
    if(-1==(fd2=open("/dev/input/event1",O_RDWR))){
        ERRLOG("fd2 open error");
    }
    //2.放入select表中
    //2.1 定义母本
    fd_set rfds,rfds_copy;
    //2.3清零
    FD_ZERO(&rfds);
    FD_ZERO(&rfds_copy);
    //2.3入表
    FD_SET(fd1,&rfds);
    FD_SET(fd2,&rfds);
    int FD_MAX=fd2+1;
    int ret=0;
    while (1)
    {
        rfds_copy=rfds;
        if(-1==(ret=select(FD_MAX,&rfds_copy,NULL,NULL,NULL))){
            ERRLOG("select error");
        }
        for(i=fd1;i

检测键盘按键test.c

#include "head.h"
#include 
int main(int argc, char const *argv[])
{
    int fd1,fd2,i;
    char buf[128]={0};
    struct input_event key_ev;
    //1.构建文件描述
    if(-1==(fd1=open("/dev/mycdev0",O_RDWR))){
        ERRLOG("fd1 open error");
    }
    if(-1==(fd2=open("/dev/input/event1",O_RDWR))){
        ERRLOG("fd2 open error");
    }
    //2.放入select表中
    //2.1 定义母本
    fd_set rfds,rfds_copy;
    //2.3清零
    FD_ZERO(&rfds);
    FD_ZERO(&rfds_copy);
    //2.3入表
    FD_SET(fd1,&rfds);
    FD_SET(fd2,&rfds);
    int FD_MAX=fd2+1;
    int ret=0;
    while (1)
    {
        rfds_copy=rfds;
        if(-1==(ret=select(FD_MAX,&rfds_copy,NULL,NULL,NULL))){
            ERRLOG("select error");
        }
        #if 0
        for(i=fd1;i

8.4.4select内核实现原理

sys_select执行流程分析

US:
 select(fd2+1,&rfds,NULL,NULL,NULL);
 //select返回的条件?
 //1.有文件描述符就绪
 //2.超时时间到了
 //3.收到了信号
-------------------------------------------------------------
VFS:sys_select
    1.判断n是否合法(n<0直接退出,n>maxfd,n=maxfd);
 2.在内核空间分配6张表的内存
    (前三张表保存的用户传递到内核的表,后三张表是保存就绪的文件描述符表)
    3.遍历文件描述符
      mask = rfds->fd-->fd_array[fd]-->file-->poll(file,wait);
    4.判断mask的值,如果文件描述符表中的所有文件描述符对应poll函数返回的mask
      都为0,说明所有硬件的数据都没有准备好,此时进程休眠。如果有mask值不为0,
      将这个文件描述符放到就绪的跳中,执行第6步骤。
    5.如果休眠的进程被唤醒,需要再次遍历文件描述符 
        mask = rfds->fd-->fd_array[fd]-->file-->poll(file,wait);
   找出mask不为0的文件描述符,将文件描述符放到就绪的表中
 6.将就绪的文件描述拷贝到用户空间
-------------------------------------------------------------
KS:fops: //应用层select/poll/epoll都是调用驱动的poll
 __poll_t (*poll) (struct file *file, struct poll_table_struct *wait)
    {
     //1.定义返回值的变量
        __poll_t mask=0;
        //2.调用poll_wait函数(构造等待队列,不会休眠)
        poll_wait(file,等待队列头,wait);
        //3.根据数据是否准备好,来决定是否置位mask
        if(condition){
         mask |= EPOLLIN;  //EPOLLIN可读 EPOLLOUT可写
        }
        //4.返回mask
        return mask;
    }

问:select/poll/epoll有什么区别?

select特点:(结构体)

  1. select监听的文件描述符最大是1024个
  2. select有清空表的过程,需要反复从用户空间向内核空间拷贝表,效率低
  3. select对应的进程从休眠被唤醒之后,需要再次遍历文件描述找出就绪的文件描述符,效率低。

poll的特点:(链表)

  1. poll监听的文件描述符没有个数限制
  2. poll没有清空表的过程,不需要反复拷贝,效率高
  3. poll对应的进程从休眠被唤醒之后,需要再次遍历文件描述找出就绪的文件描述符,效率低。

epoll的特点:(红黑树+双向链表)

  1. epoll监听的文件描述符没有个数限制
  2. epoll没有清空表的过程,不需要反复拷贝,效率高
  3. epoll对应的进程从休眠被唤醒之后,可以直接拿到就绪的文件描述符,不需要遍历,效率高

8.5异步通知IO模型

8.5.1异步通知IO模型简介

异步通知IO模型:当底层硬件的数据准备好的时候会产生硬件中断,在中断

的处理函数中给上层的进程发送信号(SIGIO),当进程收到信号之后就会执行

信号处理函数(捕捉),在信号处理函数中将数据读取到用户空间即可。

8.5.2异步通知IO模型的实现流程

US:
 fd = open("/dev/mycdev0",O_RDWR);
 void signal_handle(int signo)
    {
        //信号处理函数
    }

 //1.调用signal
 signal(SIGIO,signal_handle);
 
 //2.调用驱动的fasync函数
 unsigned int flags = fcntl(fd,F_GETFL);
 fcntl(fd,F_SETFL,flags|FASYNC);

 //3.告诉内核接收信号的进程是当前进程
 fcntl(fd,F_SETOWN,getpid());
----------------------------------------------------------
VFS: sys_fcntl
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
     err = do_fcntl(fd, cmd, arg, f.file);
   switch (cmd) {
            case F_GETFL:
                err = filp->f_flags;
                break;
            case F_SETFL:
                err = setfl(fd, filp, arg); //arg = filp->flags | FASYNC
                break;
         }

static int setfl(int fd, struct file * filp, unsigned long arg)     
     if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {                                  
        error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
     }
----------------------------------------------------------
KS:fops:
 int (*fasync) (int fd, struct file *file, int on)
    {
        //构造异步通知的队列
     return fasync_helper(fd,file,on,&fapp);
    }
 //发信号的函数
 void kill_fasync(struct fasync_struct **fp, int sig, int band)
 grep ".fasync =" * -nR

8.5.3异步通知IO模型的实例

mycdev.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define COUNT 3
#define CNAME "mycdev"

struct cdev* cdev;
int major = 0;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
struct fasync_struct *fapp;

int mycdev_open(struct inode* inode, struct file* file)
{

    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    // 将数据拷贝到用户空间
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}

ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    // 发信号
    kill_fasync(&fapp, SIGIO, POLL_IN);
    return size;
}

//fasync
int mycdev_fasync(int fd, struct file* file, int on)
{
    // 初始化异步通知队列
    return fasync_helper(fd, file, on, &fapp);
}

int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .fasync = mycdev_fasync,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev_alloc error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.cdev成员初始化
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major > 0) {
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
    } else {
        ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("cdev_add error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < COUNT; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i + minor), NULL, "%s%d", CNAME, i);
        if (IS_ERR(dev)) {
            printk("device_create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }

    return 0; /*********************别忘记写!!************************/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < COUNT; i++) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
test.c
#include 
int fd;
char buf[128] = {0};
// 信号处理函数
void signal_handler(int signo)
{
    // 将数据读取到用户空间
    if(signo == SIGIO){
        memset(buf, 0, sizeof(buf));
        read(fd, buf, sizeof(buf));
        printf("buf = %s\n", buf);
    }
}
int main(int argc, const char* argv[])
{
    if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
        PRINT_ERR("open error");

    // 1.使用signal函数为SIGIO指定处理方式
    if (SIG_ERR == signal(SIGIO, signal_handler))
        PRINT_ERR("signal error");

    // 2.调用驱动的fasync函数
    unsigned int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);

    // 3.指定接收信号的进程
    fcntl(fd, F_SETOWN, getpid());

    // 4.进程可以执行任意它想执行的任务
    while (1) {
        sleep(1);
    }
    return 0;
}

8.5.4应用程序判断POLL_IN或POLL_OUT事件

mycdev.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define COUNT 3
#define CNAME "mycdev"

struct cdev* cdev;
int major = 0;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
struct fasync_struct* fapp;

int mycdev_open(struct inode* inode, struct file* file)
{
    printk(KERN_ERR "%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    // 将数据拷贝到用户空间
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy to user error\n");
        return -EIO;
    }
    return size;
}

ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy from user error\n");
        return -EIO;
    }
    if (kbuf[0] == '0') {
        // 发信号
        kill_fasync(&fapp, SIGIO, POLL_IN);
    } else if (kbuf[0] == '1') {
        // 发信号
        kill_fasync(&fapp, SIGIO, POLL_OUT);
    }

    return size;
}

// fasync
int mycdev_fasync(int fd, struct file* file, int on)
{
    // 初始化异步通知队列
    return fasync_helper(fd, file, on, &fapp);
}

int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .fasync = mycdev_fasync,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev_alloc error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.cdev成员初始化
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (major > 0) {
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
    } else {
        ret = alloc_chrdev_region(&devno, 0, COUNT, CNAME);
        if (ret) {
            printk("register_chrdev_region error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("cdev_add error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class_create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < COUNT; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i + minor), NULL, "%s%d", CNAME, i);
        if (IS_ERR(dev)) {
            printk("device_create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }
    return 0; /*********************别忘记写!!************************/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < COUNT; i++) {
        device_destroy(cls, MKDEV(major, minor + i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

test.c

#include 
int fd;
char buf[128] = { 0 };
// 信号处理函数
// void signal_handler(int signo)
void signal_handler(int signo, siginfo_t* info, void* dev)
{
    switch (info->si_code) {
    case 1: // POLL_IN
        printf("我是应用程序,我收到底层上报的POLL_IN\n");
        if (signo == SIGIO) {
            memset(buf, 0, sizeof(buf));
            read(fd, buf, sizeof(buf));
            printf("buf = %s\n", buf);
        }
        break;
    case 2: // POLL_OUT
        printf("我是应用程序,我收到底层上报的POLL_OUT\n");
        break;
    }
}
int main(int argc, const char* argv[])
{
    struct sigaction act;
    // 2.调用驱动的fasync函数
    if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
        PRINT_ERR("open error");

    // 1.使用signal函数为SIGIO指定处理方式
    // if (SIG_ERR == signal(SIGIO, signal_handler))
    // PRINT_ERR("signal error");
    act.sa_sigaction = signal_handler;
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGIO, &act, NULL);
    fcntl(fd, __F_SETSIG, SIGIO);

    // 2.调用驱动的fasync函数
    unsigned int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);

    // 3.指定接收信号的进程
    fcntl(fd, F_SETOWN, getpid());

你可能感兴趣的:(linux,驱动开发,运维)