Linux驱动开发(二.linux字符设备驱动)
作者:互联网
一.最小驱动框架
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)
{
//copy_to_user(ubuf,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;
int dst = 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(int)));
printf("dst value is %d\n",dst);
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 驱动信息
标签:struct,int,Linux,dev,cdev,file,linux,驱动,local 来源: https://blog.csdn.net/qq_36813351/article/details/118719040