Linux驱动开发(二.linux字符设备驱动)

目录

一.最小驱动框架

二.字符设备驱动框架

第一步:cdev_init //初始化字符设备

第二步:alloc_chrdev_region  //动态获取设备编号

第三步:cdev_add //向内核添加设备

自动mknod

1.class_create

2.device_create

三.字符设备驱动关键结构体和数据

(一).struct cdev  //字符设备结构体

(二).struct file_operations //文件操作结构体

(三).dev_t  //设备号

四.模块安装手顺

(一).安装驱动模块

(二).显示所有的驱动模块

(三).卸载驱动


一.最小驱动框架

Linux设备驱动最小的一个框架。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int __init local_init(void)
{
    return 0;
}

static void __exit local_exit(void)
{
}

module_init(local_init);
module_exit(local_exit);

MODULE_LICENSE("GPL");

二.字符设备驱动框架

我先贴出一个字符设备驱动的demo,咱们再对照demo进行分析与讲解。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
//#include <asm/uaccess.h>
#include <linux/uaccess.h>

struct cdev local_cdev;
dev_t local_dev;
static struct class *cls;
static int major_num = 0;
static int minor_num = 0;
static int local_open(struct inode *inode,struct file *file)
{
    printk(KERN_INFO"open file opertions\n");
    return 0;
}
ssize_t local_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	char kbuf[3]={1,2,3};
    copy_to_user(buf,kbuf,count); //实际上因该使用这个,用于内核上的数据复制
    //*buf = 100;
    printk("local_read\n");
    return 1;
}
static ssize_t local_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    printk("local_write\n");
    char stringdemo[count];
    size_t i = 0;
    //memcpy(stringdemo,buf,count);
    copy_from_user(stringdemo,buf,count);    //实际上因该使用这个,用于内核上的数据复制
    for ( i = 0; i < count; i++)
    {
        printk("%c",stringdemo[i]);
    }
    return 2;
}
struct file_operations local_ops ={
    .owner = THIS_MODULE,
    .open = local_open,
    .read  = local_read,
    .write = local_write,
};
static int __init local_init(void)
{
    printk("\n\n\n\n\n\n\n\n\n\n");
    //主设备编号的范围是 0--256
#if 1
    cdev_init(&local_cdev,&local_ops);    // 初始化字符设备
    alloc_chrdev_region(&local_dev,0,1,"local_dong");  // 动态获取设备编号
    local_cdev.dev = local_dev;    // 备份设备编号
    major_num = MAJOR(local_dev);
    minor_num = MINOR(local_dev);
    printk("local_dev :%d major_num :%d minor_num :%d\n",local_dev,major_num,minor_num);
    cdev_add(&local_cdev,local_dev,1);    // 向内核添加设备
    printk("cdev_add\n");
#elif
    major_num = 0;
    minor_num = 0;
    local_cdev.dev = local_dev;    // 备份设备编号
    local_dev = MKDEV(major_num,minor_num); // 获取设备编号
    register_chrdev_region(local_dev,1,"local_dong2");    // 静态获取设备编号
    cdev_init(&local_cdev,&local_ops);
    cdev_add(local_cdev,local_dev,1);    // 向内核添加设备
#else
    major_num = register_chrdev(0,"local_dong",local_ops);    老式方法一步到位
#endif

    cls = class_create(THIS_MODULE, "local_dong");
    printk("class_create\n");
    //class_device_create(cls, NULL, MKDEV(major_num, 0), NULL, "local_dong0");  //Linux2.6.27之前时用这个
    device_create(cls, NULL, MKDEV(major_num, minor_num), NULL,"local_dong0");  //目前用这个
    printk("device_create\n");
    return 0;
}

static void __exit local_exit(void)
{
    // class_device_destroy(cls, MKDEV(major_num, 0));
    device_destroy(cls,MKDEV(major_num, 0));
    printk("device_destroy\n");
    class_destroy(cls);
    printk("class_destroy\n");
    cdev_del(&local_cdev);
    printk("cdev_del\n");
    unregister_chrdev_region(MKDEV(major_num, 0), 1);
    printk("unregister_chrdev_region\n");
}


module_init(local_init);
module_exit(local_exit);

MODULE_LICENSE("GPL");



/**
 * 
 * class_device_create();
 * class_device_destroy();
 * 在2.6.27中变为:
 * device_create()
 * device_destroy()
*/

Makefile

这个makefile我也记不住是在哪里复制粘贴的了,反正都差不多。

$(warning KERNELRELEASE = $(KERNELRELEASE))
 
ifeq ($(KERNELRELEASE),)
 
#内核的源码路径, ?= 条件赋值, uname -r  得到内核的版本号
KERNELDIR ?= /lib/modules/$(shell uname -r)/build  
 
# := 立即赋值, 得到当前的绝对路径
PWD := $(shell pwd)
 
 
# -C 切换工作路径, make -c
modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
 
clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules*
 
.PHONY: modules  clean
 
else
	  # 生成模块
       obj-m := 2021070601.o 
	
endif
 

App

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


int main(int argc,char *argv[])
{
    int fd = 0;
    char dst[3] = {0,0,0};
    fd = open("/dev/local_dong0",O_RDWR);
    if(fd < 0){
		printf("file open failed!\r\n");
        printf("fd %d\n",fd);
		return -1;
	}
    printf("read file return is %d\n",read(fd,dst,sizeof(char)*3));
    printf("dst value is %d\n",dst[0]);
    printf("write file return is %d\n",write(fd,"helloworld",11));

    close(fd);

    return 0;
}

三部分代码全部都贴在这里,感兴趣的话可以跑一下。

首先我们讲一下第一部分,驱动部分。

驱动部分是本篇文章的核心,其余两个可看可不看。

字符设备驱动的注册分为三步。

第一步:cdev_init //初始化字符设备

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}

cdev_init函数首先将传入的数组初始化,将fire_operation操作数组与字符设备的结构体绑定。

第二步:alloc_chrdev_region  //动态获取设备编号

设备编号dev_t是一个无符号32位整形数,前12位为主设备编号,后20位为次设备编号。

这个函数的第三个参数就是你lsmod显示驱动的名称。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
{
	struct char_device_struct *cd;
	cd = __register_chrdev_region(0, baseminor, count, name);
	if (IS_ERR(cd))
		return PTR_ERR(cd);
	*dev = MKDEV(cd->major, cd->baseminor);
	return 0;
}

第三步:cdev_add //向内核添加设备

这个函数执行完毕时,lsmod就会显示出来所有的字符设备驱动,这时你的字符设备编号和字符设备名称都会看到。

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	int error;

	p->dev = dev;
	p->count = count;

	if (WARN_ON(dev == WHITEOUT_DEV))
		return -EBUSY;

	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}

执行完这几个函数时你在/dev目录还是找不到你自己写的驱动,你还需要mknod /dev/xxx  c   主设备编号  次设备编号。这个时候你就能在/dev文件下找到你的驱动了,在这个文件夹下你也能使用你的驱动了。这个地方的主设备编号就是你第二步时的字符设备编号。

每次你都需要手动的mknod去添加设备这个就很麻烦,所以自动添加的方法如下。

自动mknod

1.class_create

创建class这个就是一类设备的集合,这个class下边可以挂在非常多的同类型设备驱动。class在/sys/class/下创建一个自定义名称的文件夹,这个文件夹就可以理解为class。

2.device_create

将你的设备驱动挂载到class下边,并将驱动显示到/dev下边。

到这里一个字符设备的创建到显示到/dev文件夹的流程就走完了。

三.字符设备驱动关键结构体和数据

(一).struct cdev  //字符设备结构体

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;

这个结构体里边含有file_operations,dev_t设备号。

(二).struct file_operations //文件操作结构体

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iopoll)(struct kiocb *kiocb, bool spin);
	int (*iterate) (struct file *, struct dir_context *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	__poll_t (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	unsigned long mmap_supported_flags;
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
				   struct file *file_out, loff_t pos_out,
				   loff_t len, unsigned int remap_flags);
	int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

这个里边含有read,write,open,close等操作,使用时

(三).dev_t  //设备号

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

MAJOR(dev)中的dev就是无符号32位整数值的设备号,MAJOR(dev)结果是主设备标号,获取dev前12位的值。

MINOR(dev)结果是次设备标号,获取dev后20位的值。

MKDEV(ma,mi)中ma是主设备号,mi是次设备号,MKDEV(ma,mi)得的结果是设备编号。

四.模块安装手顺

(一).安装驱动模块

执行驱动文件的makefile,生成xxx.ko文件,在执行insmod xxx.ko,安装驱动模块。

(二).显示所有的驱动模块

执行lsmod,查看你的驱动模块名称

(三).卸载驱动

执行rmmod xxx,卸载对应的驱动模块

insmod 安装驱动                                                 rmmod                  卸载驱动

lsmod   显示所有的模块                                      modinfo                驱动信息

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式吴彦祖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值