Linux字符设备驱动_进阶

本文详细介绍了字符设备驱动的实现过程,包括自动创建设备节点的方法、ioctl函数的应用、字符设备驱动的分步实现流程等核心内容,并提供了丰富的实例帮助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.自动创建设备节点

1.1自动创建设备节点的历史演变

(1)mknod (rcS)

(2)devfs (2.4内核)

(3)udev/mdev (2.6内核版本之后)

1.2udev/mdev的原理

mdev就是一个轻量级的udev (和udev的原理一样),常用在嵌入式设备上。
在这里插入图片描述

1.3内核中提交信息的函数API

struct class * class_create(owner, name)	
功能:提交目录信息
参数:
    @owner:THIS_MODULE (以后遇到owner就填写THIS_MODULE即可,它是和入口出口相关的宏)
	@name:目录名
返回值:成功返回结构体指针,失败返回错误码指针 (void *)-12;

//IS_ERR:判断是否是错误,判断这个地址是否大于 > (unsigned long)(-4095)
//PTR_ERR:错误码指针还原为错误码
//ERR_PTR:错误码转为错误码指针
struct class *cls;  
cls = class_create(THIS_MODULE,"hello");
if(IS_ERR(cls)){
    printk("class create error\n");
    return  PTR_ERR(cls);
}

void class_destroy(struct class *cls)
功能:销毁目录
参数:
    @cls:class的指针
返回值:无

struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
功能:向上提交设备节点的信息
参数:
    @class:class结构体指针
	@parent:NULL
    @devt:设备号  
        	MKDEV(ma,mi) :根据主次设备号合成设备号
        	MAJOR(dev) :得到主设备号(dev设备号)
        	MINOR(dev):得到次设备号(dev设备号)
	@drvdata:NULL
    @fmt:节点名  "mycdev%d",i
返回值:成功返回结构体指针,失败返回错误码指针

void device_destroy(struct class *class, dev_t devt)
功能:销毁设备信息
参数:
    @class:class结构体指针
	@devt:设备号
返回值:无

在这里插入图片描述

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

myled.h

#ifndef __MYLED_H__
#define __MYLED_H__

#define GPIOE_PHY_BASE 0x50006000 //(moder odr)
#define RCC_ENB      0x50000a28 //rcc_enb
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;


#endif

myled.c

#include "myled.h"
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/uaccess.h>

#define CNAME "myled"

int major = 0;
char kbuf[128] = { 0 };
gpio_t* gpioe = NULL;
unsigned int* rcc;
struct class* cls;
struct device* dev;
#define LED1_ON (gpioe->ODR |= (1 << 10)) // led1 on
#define LED1_OFF (gpioe->ODR &= ~(1 << 10)) // led1 off

int myled_open(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    // 1.映射地址
    gpioe = ioremap(GPIOE_PHY_BASE, sizeof(gpio_t));
    if (gpioe == NULL) {
        printk("ioremap gpioe base error\n");
        return -ENOMEM;
    }

    rcc = ioremap(RCC_ENB, 4);
    if (rcc == NULL) {
        printk("ioremap rcc error\n");
        return -ENOMEM;
    }
    // 2.初始化灯
    *rcc |= (1 << 4); // rcc enable gpioe
    gpioe->MODER &= ~(3 << 20); // clear bit 20-21
    gpioe->MODER |= (1 << 20); // output
    gpioe->ODR &= ~(1 << 10); // led1 off

    return 0;
}

ssize_t myled_read(struct file* filp,
    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 data to user error\n");
        return -EINVAL;
    }

    return size;
}

ssize_t myled_write(struct file* filp,
    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 data from user error\n");
        return -EINVAL;
    }

    kbuf[0] == 1 ? LED1_ON : LED1_OFF;

    return size;
}

int myled_close(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    gpioe->ODR &= ~(1 << 10); // led1 off

    iounmap(gpioe);
    iounmap(rcc);
    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 i;
    //创建字符设备驱动
    major = register_chrdev(0, CNAME, &fops);
    if (major < 0) {
        printk("register char device driver error\n");
        return major;
    }
    //自动创建设备节点
    cls = class_create(THIS_MODULE, "hello");
    if (IS_ERR(cls)) {
        printk("class create error\n");
        return PTR_ERR(cls);
    }

    for (i = 0; i < 3; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
        if (IS_ERR(dev)) {
            printk("device create error\n");
            return PTR_ERR(dev);
        }
    }
    return 0;
}
static void __exit myled_exit(void)
{
    int i;
    for (i = 0; i < 3; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    //销毁字符设备驱动
    unregister_chrdev(major, CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

char buf[128] = {0};
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("/dev/myled0",O_RDWR)) < 0){
		perror("open error");
		exit(EXIT_FAILURE);
	}
	while(1){
		buf[0] = !buf[0];
		write(fd,buf,sizeof(buf));
		sleep(1);
	}

	close(fd);
	return 0;
}

2.ioctl函数的使用

2.1为什么需要ioctl

ioctl:设备的控制,linux内核想把数据的读写和设备的控制拆分开来。

以后设备的控制就通过ioctl完成,数据的读写通过read/write完成。

例如:可以通过ioctl设置摄像头的工作模式(拍摄图片的大小,拍摄图片的格式),

当拍摄完照片之后,通过read函数将图片读取到用户空间。

2.2ioctl的API接口

us:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
功能:设备的控制
参数:
    @fd:文件描述符
    @request:请求码
	@... :可以不填写,如果填写填写地址
返回值:成功返回0,失败返回-1置位错误码
-------------------------------------------------------
ks:
fops:long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long args)
{
    switch(cmd){
        case LED1_ON: //将LED1灯点亮
            break; 
        case LED1_OFF: //将LED1灯熄灭
            break;
    }
}

2.3请求码的定义规则

在这里插入图片描述

2.4ioctl实例

cmd.h

#ifndef __CMD_H__
#define __CMD_H__

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

#define LED1_ON _IO('g',0)
#define LED1_OFF _IO('g',1)
#define UACCESS_STRUCT _IOW('g',2,image_t)
#define GET_CMD_SIZE(cmd) ((cmd>>16)&0x3fff)

#endif

myled.c

#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include "cmd.h"
#define CNAME "myled"

int major = 0;
char kbuf[128] = { 0 };
unsigned int* rcc;
struct class* cls;
struct device* dev;


int myled_open(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

ssize_t myled_read(struct file* filp,
    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 data to user error\n");
        return -EINVAL;
    }

    return size;
}

ssize_t myled_write(struct file* filp,
    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 data from user error\n");
        return -EINVAL;
    }


    return size;
}
long myled_ioctl (struct file *file,
     unsigned int cmd, unsigned long args)
{
    int ret;
    image_t img;
    switch(cmd){
        case LED1_ON:
            printk("LED1 ON\n");
            break;
        case LED1_OFF:
            printk("LED1 OFF\n");
            break;
        case UACCESS_STRUCT:
            ret = copy_from_user(&img,(void *)args,
                GET_CMD_SIZE(UACCESS_STRUCT));
            if(ret){
                printk("copy data from user error\n");
                return -EINVAL;
            }
            printk("width = %d,high = %d\n",img.width,img.high);
            break;
    }
    return 0;
}
int myled_close(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

const struct file_operations fops = {
    .open = myled_open,
    .read = myled_read,
    .write = myled_write,
    .unlocked_ioctl = myled_ioctl,
    .release = myled_close,
};

static int __init myled_init(void)
{
    int i;
    //创建字符设备驱动
    major = register_chrdev(0, CNAME, &fops);
    if (major < 0) {
        printk("register char device driver error\n");
        return major;
    }
    //自动创建设备节点
    cls = class_create(THIS_MODULE, "hello");
    if (IS_ERR(cls)) {
        printk("class create error\n");
        return PTR_ERR(cls);
    }

    for (i = 0; i < 3; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
        if (IS_ERR(dev)) {
            printk("device create error\n");
            return PTR_ERR(dev);
        }
    }
    return 0;
}
static void __exit myled_exit(void)
{
    int i;
    for (i = 0; i < 3; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    //销毁字符设备驱动
    unregister_chrdev(major, CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include "cmd.h"
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("/dev/myled0",O_RDWR)) < 0){
		perror("open error");
		exit(EXIT_FAILURE);
	}

	ioctl(fd,LED1_ON);
	ioctl(fd,LED1_OFF);
	image_t img = {
		1024,600
	};
	ioctl(fd,UACCESS_STRUCT,&img);

	close(fd);
	return 0;
}

2.5练习

1.通过ioctl实现流水灯的效果

2.通过ioctl用内核空间向用户空间传递char [128];

cmd.h

#ifndef __CMD_H__
#define __CMD_H__

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

#define LED1_ON _IO('g',0)
#define LED1_OFF _IO('g',1)
#define UACCESS_STRUCT _IOW('g',2,image_t)
#define UACCESS_BUFFER _IOR('g',3,char [128])
#define GET_CMD_SIZE(cmd) ((cmd>>16)&0x3fff)
#endif

myled.h

#ifndef __MYLED_H__
#define __MYLED_H__

#define GPIOE_PHY_BASE 0x50006000 //(moder odr)
#define RCC_ENB      0x50000a28 //rcc_enb
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;
#endif

myled.c

#include "myled.h"
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include "cmd.h"
#define CNAME "myled"

int major = 0;
char kbuf[128] = { 0 };
gpio_t* gpioe = NULL;
unsigned int* rcc;
struct class* cls;
struct device* dev;
#define SET_LED1_ON (gpioe->ODR |= (1 << 10)) // led1 on
#define SET_LED1_OFF (gpioe->ODR &= ~(1 << 10)) // led1 off

int myled_open(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    // 1.映射地址
    gpioe = ioremap(GPIOE_PHY_BASE, sizeof(gpio_t));
    if (gpioe == NULL) {
        printk("ioremap gpioe base error\n");
        return -ENOMEM;
    }

    rcc = ioremap(RCC_ENB, 4);
    if (rcc == NULL) {
        printk("ioremap rcc error\n");
        return -ENOMEM;
    }
    // 2.初始化灯
    *rcc |= (1 << 4); // rcc enable gpioe
    gpioe->MODER &= ~(3 << 20); // clear bit 20-21
    gpioe->MODER |= (1 << 20); // output
    gpioe->ODR &= ~(1 << 10); // led1 off

    return 0;
}

ssize_t myled_read(struct file* filp,
    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 data to user error\n");
        return -EINVAL;
    }

    return size;
}

ssize_t myled_write(struct file* filp,
    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 data from user error\n");
        return -EINVAL;
    }

    return size;
}
long myled_ioctl (struct file *file,
     unsigned int cmd, unsigned long args)
{
    int ret;
    switch(cmd){
        case LED1_ON:
            SET_LED1_ON;
            break;
        case LED1_OFF:
            SET_LED1_OFF;
            break;
        case UACCESS_BUFFER:
            memcpy(kbuf,"this is test ioctl access buffer to user",41);
            ret = copy_to_user((void *)args,kbuf,
                GET_CMD_SIZE(UACCESS_BUFFER));
            if(ret){
                printk("access data to user error\n");
                return -EINVAL;
            }

            break;
    }
    return 0;
}
int myled_close(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    gpioe->ODR &= ~(1 << 10); // led1 off

    iounmap(gpioe);
    iounmap(rcc);
    return 0;
}

const struct file_operations fops = {
    .open = myled_open,
    .read = myled_read,
    .write = myled_write,
    .unlocked_ioctl = myled_ioctl,
    .release = myled_close,
};

static int __init myled_init(void)
{
    int i;
    //创建字符设备驱动
    major = register_chrdev(0, CNAME, &fops);
    if (major < 0) {
        printk("register char device driver error\n");
        return major;
    }
    //自动创建设备节点
    cls = class_create(THIS_MODULE, "hello");
    if (IS_ERR(cls)) {
        printk("class create error\n");
        return PTR_ERR(cls);
    }

    for (i = 0; i < 3; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
        if (IS_ERR(dev)) {
            printk("device create error\n");
            return PTR_ERR(dev);
        }
    }
    return 0;
}
static void __exit myled_exit(void)
{
    int i;
    for (i = 0; i < 3; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    //销毁字符设备驱动
    unregister_chrdev(major, CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

test.c

#include "cmd.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
char buf[128] = { 0 };
int main(int argc, const char* argv[])
{
    int fd;
    if ((fd = open("/dev/myled0", O_RDWR)) < 0) {
        perror("open error");
        exit(EXIT_FAILURE);
    }

    ioctl(fd, UACCESS_BUFFER, buf);
    printf("buf = %s\n", buf);
     
	//alt+shift+f
    while (1) {
        ioctl(fd, LED1_ON);
        sleep(1);
        ioctl(fd, LED1_OFF);
        sleep(1);
    }

    close(fd);
    return 0;
}

3.字符分步实现流程

3.1字符设备驱动的框架结构

在这里插入图片描述

3.2分步实现字符设备驱动的API

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

    struct cdev cdev; 
    struct cdev *cdev = cdev_alloc(); //申请内存
    kfree(cdev); //释放内存
2.对象的初始化
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    功能:初始化字符设备驱动
    参数:
        @cdev:cdev的结构体指针
        @fops:操作方法结构体指针
    返回值:无

    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:申请到的设备号
        @basminor:次设备号的起始值
        @count:设备的个数
        @name:设备名 cat /proc/devices
    返回值:成功返回0,失败返回错误码

    void unregister_chrdev_region(dev_t from, unsigned count)
    功能:注销设备号
    参数;
        @from:设备号的起始值
        @count:设备的个数
    返回值:无


3.字符设备驱动的注册
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    功能:注册字符设备驱动
    参数:
        @p:cdev的结构体指针
        @dev:设备号
        @count:设备的个数
    返回值:成功返回0,失败返回错误码

4.注销
    void cdev_del(struct cdev *p)
    功能:注销字符设备驱动
    参数:  
        @p:cdev的结构体指针
    返回值:无

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

mycdev.c

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define CNAME "mycdev"
#define COUNT 3
struct cdev* cdev;
int major =238;
int minor = 0;
char kbuf[128] = { 0 };
struct class *cls;
struct device *dev;
int mycdev_open(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

ssize_t mycdev_read(struct file* filp,
    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 data to user error\n");
        return -EINVAL;
    }

    return size;
}

ssize_t mycdev_write(struct file* filp,
    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 data from user error\n");
        return -EINVAL;
    }

    return size;
}

int mycdev_close(struct inode* inode, struct file* filp)
{
    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("alloc cdev memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }

    // 2.对象初始化
    cdev_init(cdev, &fops);

    // 3.设备号的申请
    if (major == 0) {
        //动态申请
        ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
        if (ret) {
            printk("dynamic:request device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    } else {
        //静态指定
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("static:request device number error\n");
            goto ERR2;
        }
    }

    // 4.字符设备驱动的注册
    ret = cdev_add(cdev,MKDEV(major,minor),COUNT);
    if(ret){
        printk("register chardev 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 = minor; i < minor+3; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
        if (IS_ERR(dev)) {
            printk("device create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }
    
    return 0;  /*!!!!!!!!!!!!!!不要忘记!!!!!!!!!!!!!!!!!!!!!!!*/
    
ERR5:
    for(--i;i>=minor;i--){
        device_destroy(cls,MKDEV(major,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 = minor; i < minor+3; i++) {
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);
    //字符设备驱动的注销
    cdev_del(cdev);
    //设备号释放
    unregister_chrdev_region(MKDEV(major,minor),COUNT);
    //释放cdev的内存空间
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

4.file结构体的功能

4.1file的引入

open(“/dev/myled0”)—>inode号—>inode结构体—>cdev---->fops—>驱动的open函数

read(fd,buf,sizeof(buf))

write(fd,buf,sizeof(buf))

close(fd)

read、write、close不不是通过文件找到的驱动,而是通过文件描述符fd找到的驱动的read,write,close

总结:fd是和file有关系

2.2fd和file结构体的关系

fd是在进程中调用open函数的时候产生的,当进程结束的时候,文件描述符就会被销毁了。

所以fd一定是保存在进程的结构体中的。进程的结构体是task_struct

struct task_struct {
	volatile long state;
    //进程的状态 运行态,可中断的等待态,不可中断的等待态,停止态,僵尸态,死亡态
	int		on_cpu;
    //标识进程在那个处理器上运行
    int		prio;
    //进程的优先级
	pid_t				pid;
    //进程的标识符
	pid_t				tgid;
    //组id
	struct task_struct __rcu	*real_parent;
	//真实的父进程
	struct task_struct __rcu	*parent;
	//父进程
	struct list_head		children;
    //子进程
	struct list_head		sibling;
    //兄弟进程
	struct task_struct		*group_leader;
    //组长进程
	struct files_struct		*files;
    //这个结构体就是记录的打开文件的所有的信息

};


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

fd--->fd_array[fd]--->file--->f_op--->驱动的open read  write close
    
只要文件被打开一次就会产生一个file结构体,
file结构体是用来记录打开文件的信息的
struct file {
	struct path		f_path;   //路径的结构体
	struct inode		*f_inode;	//文件的结构体
	const struct file_operations	*f_op; //操作方法结构体
	unsigned int 		f_flags;   //打开文件的权限,文件是阻塞还是非阻塞打开
	fmode_t			f_mode;        //文件的权限
	loff_t			f_pos;         //文件光标的位置
	void			*private_data; //私有数据,可以用作函数间传参
};

4.3总结file

在使用open打开一个文件的时候,其实是在内核中创建了一个file的结构体,这个

结构体记录的打开文件的所有的信息,将这个file结构体放入到fd_arrary数组中,

数据的下标就是文件描述符,将下标返回给用户。用户通过fd找到驱动的操作方法

结构体中驱动函数的流程如下:

fd—>fd_array[fd]—>file—>f_op—>驱动的open read write close

5.设备号的查找方式(哈希)

设备号在内核中通过哈希表来存储。设备号在查找的时候通过哈希查找的方式

要比单链表更快,因为相当于将一个链表拆分成了255个短的链表。
在这里插入图片描述

6作业:

1.完成分步实现字符设备驱动的实例

2.通过设备文件识别设备

echo 1 > /dev/myled0   led1亮  (open write close)
echo 0 > /dev/myled0   led1灭  (open write close)
echo 1 > /dev/myled1   led2亮  (open write close)
echo 0 > /dev/myled1   led2灭  (open write close)
echo 1 > /dev/myled2   led3亮  (open write close)
echo 0 > /dev/myled2   led3灭  (open write close)
    
myled0   myled1   myled2
-------------------------------
        myled(驱动)
-------------------------------
 led1    led2      led3

6.1根据设备文件识别设备

echo 1 > /dev/myled0   led1亮  (open write close)
echo 0 > /dev/myled0   led1灭  (open write close)
echo 1 > /dev/myled1   led2亮  (open write close)
echo 0 > /dev/myled1   led2灭  (open write close)
echo 1 > /dev/myled2   led3亮  (open write close)
echo 0 > /dev/myled2   led3灭  (open write close)
    
myled0   myled1   myled2
240,0    240,1    240,2
---------------------------------------------------------------------------------------------
只要在存在一个文件,内核中一定会对应的inode结构体,这个结构体就是用来描述文件的
inode0   inode1   inode2
i_rdev   i_rdev   i_rdev
---------------------------------------------------------------------------------------------
myled(驱动):
	myled_open(struct inode* inode, struct file* filp)
    {
        int curno;
        curno = MINOR(inode->i_rdev);
        //curno=0
        //curno=1
       	//curno=2
    }
---------------------------------------------------------------------------------------------
 led1    led2      led3

myled.c

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define CNAME "myled"
#define COUNT 3
struct cdev* cdev;
int major = 238;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
int curno;
enum {
    LED1,
    LED2,
    LED3
};
int myled_open(struct inode* inode, struct file* filp)
{
    curno = MINOR(inode->i_rdev);
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

ssize_t myled_read(struct file* filp,
    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 data to user error\n");
        return -EINVAL;
    }

    return size;
}

ssize_t myled_write(struct file* filp,
    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 data from user error\n");
        return -EINVAL;
    }

    // echo "1" > /dev/myled0
    switch (curno) {
    case LED1:
        kbuf[0] == '1' ? printk("LED1 ON\n") : printk("LED1 OFF\n");
        break;
    case LED2:
        kbuf[0] == '1' ? printk("LED2 ON\n") : printk("LED2 OFF\n");
        break;
    case LED3:
        kbuf[0] == '1' ? printk("LED3 ON\n") : printk("LED3 OFF\n");
        break;
    }

    return size;
}

int myled_close(struct inode* inode, struct file* filp)
{
    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.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("alloc cdev memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }

    // 2.对象初始化
    cdev_init(cdev, &fops);

    // 3.设备号的申请
    if (major == 0) {
        //动态申请
        ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
        if (ret) {
            printk("dynamic:request device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    } else {
        //静态指定
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("static:request device number error\n");
            goto ERR2;
        }
    }

    // 4.字符设备驱动的注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("register chardev 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 = minor; i < minor + 3; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
        if (IS_ERR(dev)) {
            printk("device create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }
    return 0; /*!!!!!!!!!!!!!!不要忘记!!!!!!!!!!!!!!!!!!!!!!!*/
ERR5:
    for (--i; i >= minor; i--) {
        device_destroy(cls, MKDEV(major, 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 myled_exit(void)
{
    int i;
    //注销设备节点
    for (i = minor; i < minor + 3; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    //字符设备驱动的注销
    cdev_del(cdev);
    //设备号释放
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    //释放cdev的内存空间
    kfree(cdev);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

, COUNT);
if (ret) {
printk(“register chardev 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 = minor; i < minor + 3; i++) {
    dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
    if (IS_ERR(dev)) {
        printk("device create error\n");
        ret = PTR_ERR(dev);
        goto ERR5;
    }
}
return 0; /*!!!!!!!!!!!!!!不要忘记!!!!!!!!!!!!!!!!!!!!!!!*/

ERR5:
for (–i; i >= minor; i–) {
device_destroy(cls, MKDEV(major, 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 myled_exit(void)
{
int i;
//注销设备节点
for (i = minor; i < minor + 3; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
//字符设备驱动的注销
cdev_del(cdev);
//设备号释放
unregister_chrdev_region(MKDEV(major, minor), COUNT);
//释放cdev的内存空间
kfree(cdev);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE(“GPL”);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值