lv14 注册字符设备 3

发布时间:2024年01月01日

1 注册字符设备

1.1 结构体介绍

struct cdev
{
    struct kobject kobj;//表示该类型实体是一种内核对象
    struct module *owner;//填THIS_MODULE,表示该字符设备从属于哪个内核模块
    const struct file_operations *ops;//指向空间存放着针对该设备的各种操作函数地址
    struct list_head list;//链表指针域,使用hash链表来管理这么多设备
    dev_t dev;//设备号
    unsigned int count;//设备数量
};

1.2 定义方式

自己定义的结构体中必须有一个成员为 struct cdev cdev,两种方法定义一个设备:

  1. 直接定义:定义结构体全局变量。(一般写程序主要使用直接定义)
  2. 动态申请:
struct cdev * cdev_alloc()

?1.3 初始化

//设备操作函数集合
struct file_operations 
{
 ? struct module *owner;???????????//填THIS_MODULE,表示该结构体对象从属于哪个内核模块
 ? int (*open) (struct inode *, struct file *); //打开设备
 ? int (*release) (struct inode *, struct file *);  //关闭设备
 ? ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);    //读设备
 ? ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);??? //写设备
 ? loff_t (*llseek) (struct file *, loff_t, int);       //定位
 ? long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
 ? unsigned int (*poll) (struct file *, struct poll_table_struct *);    //POLL机制,实现多路复用的支持
 ? int (*mmap) (struct file *, struct vm_area_struct *);?//映射内核空间到用户层
 ? int (*fasync) (int, struct file *, int);?//信号驱动
 ? //......
};

//使用这套操作函数来操作设备
void cdev_init(struct cdev *cdev,const struct file_operations *fops)

该对象各个函数指针成员都对应相应的系统调用函数,应用层通过调用系统函数来间接调用这些函数指针成员指向的设备驱动函数:

一般定义一个struct file_operations类型的全局变量并用自己实现各种操作函数名对其进行初始化

int cdev_add(struct cdev *p,dev_t dev,unsigned int count)
功能:将指定字符设备添加到内核
参数:
    p:指向被添加的设备
    dev:设备号
    count:设备数量,一般填1
void cdev_del(struct cdev *p)
功能:从内核中移除一个字符设备
参数:
    p:指向被移除的字符设备

1.4 小结:

字符设备驱动开发步骤:

  1. 如果设备有自己的一些控制数据,则定义一个包含struct cdev cdev成员的结构体struct mydev,其它成员根据设备需求,设备简单则直接用struct cdev

  2. 定义一个struct mydev或struct cdev的全局变量来表示本设备;也可以定义一个struct mydev或struct cdev的全局指针(记得在init时动态分配)

  3. 定义三个全局变量分别来表示主设备号、次设备号、设备数

  4. 定义一个struct file_operations结构体变量,其owner成员置成THIS_MODULE

  5. module init函数流程:a. 申请设备号 b. 如果是全局设备指针则动态分配代表本设备的结构体元素 c. 初始化struct cdev成员 d. 设置struct cdev的owner成员为THIS_MODULE e. 添加字符设备到内核

  6. module exit函数:a. 注销设备号 b. 从内核中移除struct cdev c. 如果如果是全局设备指针则释放其指向空间

  7. 编写各个操作函数并将函数名初始化给struct file_operations结构体变量

验证操作步骤:

  1. 编写驱动代码mychar.c

  2. make生成ko文件

  3. insmod内核模块

  4. 查阅字符设备用到的设备号(主设备号):cat /proc/devices | grep 申请设备号时用的名字

  5. 创建设备文件(设备节点) : mknod /dev/??? c 上一步查询到的主设备号 代码中指定初始次设备号

  6. 编写app验证驱动(testmychar_app.c)

  7. 编译运行app,dmesg命令查看内核打印信息

示例

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>

int major = 11;
int minor = 0;
int mychar_num = 1;

struct cdev mydev;

int mychar_open(struct inode *pnode, struct file *pfile)
{
    printk("mychar_open is called\n");
    return 0;
}

int mychar_close(struct inode *pnode, struct file *pfile)
{
    printk("mychar_close is called\n");
    return 0;
}

//结构体初始化:部分变量赋值初始化
struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = mychar_open,
    .release = mychar_close,
};

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

    /* 申请设备号 */
    ret = register_chrdev_region(devno, mychar_num, "mychar");
    if (ret) {
        ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
        if (ret) {
            printk("get devno failed\n");
            return -1;
        }
		major = MAJOR(devno); // 容易遗漏,注意
    }

    /* 给struct cdev对象指定操作函数集 */
    cdev_init(&mydev, &myops);

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

    return 0;
}

void __exit mychar_exit(void)
{
    dev_t devno = MKDEV(major, minor);

    cdev_del(&mydev);

    unregister_chrdev_region(devno, mychar_num);
}

//表示支持GPL的开源协议
MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

testmychar_app.c

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

#include <stdio.h>


int main(int argc,char *argv[])
{
	int fd = -1;

	if(argc < 2)
	{
		printf("The argument is too few\n");
		return 1;
	}

	fd = open(argv[1],O_RDWR);
	if(fd < 0)
	{
		printf("open %s failed\n",argv[1]);
		return 2;
	}

	close(fd);
	fd = -1;
	return 0;
}

Makefile

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)


modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else
CONFIG_MODULE_SIG=n
obj-m += mychar.o

endif

?

文章来源:https://blog.csdn.net/m0_60718520/article/details/135325971
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。