一、静态申请字符类设备号
1、包括主设备号和次设备号
2、register_chrdev_region() 是提前知道设备的主次设备号,再去申请设备号。
3、字符设备函数在文件“include/linux/fs.h”中
extern int register_chrdev_region(dev_t, unsigned, const char *);
dev_t :设备号, 高12位为主设备号,低20位为次设备号
unsigned:设备数
const char * : 设备名
二、动态申请字符类设备号
1、alloc_chrdev_region() 是动态分配主次设备号
2、字符设备函数在文件“include/linux/fs.h”中
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
dev_t *:提起设备号
unsigned:设备数
const char * : 设备名
三、注销字符设备号
1、字符设备函数在文件“include/linux/fs.h”中
extern void unregister_chrdev_region(dev_t, unsigned);
dev_t: 设备号
unsigned :设备数
四、宏定义
include/linux/kdev_t.h
1、生成设备号
#define MKDEV(ma,mi)
2、提起主设备号
#define MAJOR(dev)
3、提起次设备号
#define MINOR(dev)
五、注册字符类设备
1、字符设备初始化函数cdev_init
– 在头文件include/linux/cdev.h中
– 参数1:cdev字符设备文件结构体
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
– 参数2:file_operations结构体
– 注册设备本质是向linux设备文件中添加数据,这些数据需要初始化
2、字符设备注册函数cdev_add
– 在头文件include/linux/cdev.h中
– 参数1:cdev字符设备文件结构体
– 参数2:设备号
– 参数3:设备范围大小
– 向系统注册设备,也就是向linux系统添加数据
3、卸载设备函数cdev_del
– 参数1:cdev结构体
– 移除字符设备
六、创建设备节点
1、函数class_create创建class类文件
2、在头文件include/linux/device.h
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
– 参数1:一般是THIS_MODULE
– 参数2:设备名称
– 创建一个设备类,用于设备节点文件的创建
– 返回一个class结构体变量
3、class结构体变量
– class是设备驱动模型中通用的设备类结构
– 在头文件include/linux/device.h的280行
struct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
struct device_attribute *dev_attrs;
struct bin_attribute *dev_bin_attrs;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, mode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
}
4、释放设备class函数class_destroy
extern void class_destroy(struct class *cls);
5、创建设备节点函数device_create
extern struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...)
– 头文件include/linux/device.h中
– 参数1:设备所属于的类
– 参数2:设备的父设备,NULL
– 参数3:设备号
– 参数4:设备数据,NULL
– 参数4:设备节点名称
6、摧毁设备节点函数device_destroy
extern void device_destroy(struct class *cls, dev_t devt);
– 参数1:设备所属于的类
– 参数2:设备号
7、释放内存函数kfree
– 参数1:数据指针
8、
– 使用命令“ls /sys/class/”可以查看到生成的class
– 使用命令“ls /dev”可以查看到生成的两个设备节点
七、字符驱动
– file_operations函数 (include/linux/fs.h)
– 如果需要不同的设备节点有不同的功能,只需要在注册设备的时候添加不同的file_operations结构体即可
– file_operations中的函数比较多,选取用的比较多的函数简单介绍
int (*open) (struct inode *, struct file *)
– 打开函数
int (*release) (struct inode *, struct file *)
– 释放close函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long)
– io控制函数
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)
– 定位函数
八、驱动 char_driver.c
/*包含初始化宏定义的头文件,代码中的module_init和module_exit在此文件中*/
#include <linux/init.h>
/*包含初始化加载模块的头文件,代码中的MODULE_LICENSE在此头文件中*/
#include <linux/module.h>
/*定义module_param module_param_array的头文件*/
#include <linux/moduleparam.h>
/*定义module_param module_param_array中perm的头文件*/
#include <linux/stat.h>
/*三个字符设备函数*/
#include <linux/fs.h>
/*MKDEV转换设备号数据类型的宏定义*/
#include <linux/kdev_t.h>
/*定义字符设备的结构体*/
#include <linux/cdev.h>
/*分配内存空间函数头文件*/
#include <linux/slab.h>
/*包含函数device_create 结构体class等头文件*/
#include <linux/device.h>
#define DEVICE_NAME "chardevnode"
#define DEVICE_MINOR_NUM 2
#define DEV_MAJOR 0
#define DEV_MINOR 0
#define REGDEV_SIZE 3000
MODULE_LICENSE("Dual BSD/GPL");
/*声明是开源的,没有内核版本限制*/
MODULE_AUTHOR("iTOPEET_dz");
/*声明作者*/
int numdev_major = DEV_MAJOR;
int numdev_minor = DEV_MINOR;
/*输入主设备号*/
module_param(numdev_major,int,S_IRUSR);
/*输入次设备号*/
module_param(numdev_minor,int,S_IRUSR);
static struct class *myclass;
struct reg_dev
{
char *data;
unsigned long size;
struct cdev cdev;
};
struct reg_dev *my_devices;
/*打开操作*/
static int chardevnode_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "chardevnode_open is success!\n");
return 0;
}
/*关闭操作*/
static int chardevnode_release(struct inode *inode, struct file *file){
printk(KERN_EMERG "chardevnode_release is success!\n");
return 0;
}
/*IO操作*/
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg);
return 0;
}
ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){
return 0;
}
ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){
return 0;
}
loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){
return 0;
}
struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = chardevnode_open,
.release = chardevnode_release,
.unlocked_ioctl = chardevnode_ioctl,
.read = chardevnode_read,
.write = chardevnode_write,
.llseek = chardevnode_llseek,
};
/*设备注册到系统*/
static void reg_init_cdev(struct reg_dev *dev,int index){
int err;
int devno = MKDEV(numdev_major,numdev_minor+index);
/*数据初始化*/
cdev_init(&dev->cdev,&my_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &my_fops;
/*注册到系统*/
err = cdev_add(&dev->cdev,devno,1);
if(err){
printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);
}
else{
printk(KERN_EMERG "cdev_add %d is success!\n",numdev_minor+index);
}
}
static int scdev_init(void)
{
int ret = 0,i;
dev_t num_dev;
printk(KERN_EMERG "numdev_major is %d!\n",numdev_major);
printk(KERN_EMERG "numdev_minor is %d!\n",numdev_minor);
if(numdev_major){
num_dev = MKDEV(numdev_major,numdev_minor);
ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME);
}
else{
/*动态注册设备号*/
ret = alloc_chrdev_region(&num_dev,numdev_minor,DEVICE_MINOR_NUM,DEVICE_NAME);
/*获得主设备号*/
numdev_major = MAJOR(num_dev);
printk(KERN_EMERG "adev_region req %d !\n",numdev_major);
}
if(ret<0){
printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",numdev_major);
}
myclass = class_create(THIS_MODULE,DEVICE_NAME);
my_devices = kmalloc(DEVICE_MINOR_NUM * sizeof(struct reg_dev),GFP_KERNEL);
if(!my_devices){
ret = -ENOMEM;
goto fail;
}
memset(my_devices,0,DEVICE_MINOR_NUM * sizeof(struct reg_dev));
/*设备初始化*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
my_devices[i].data = kmalloc(REGDEV_SIZE,GFP_KERNEL);
memset(my_devices[i].data,0,REGDEV_SIZE);
/*设备注册到系统*/
reg_init_cdev(&my_devices[i],i);
/*创建设备节点*/
device_create(myclass,NULL,MKDEV(numdev_major,numdev_minor+i),NULL,DEVICE_NAME"%d",i);
}
printk(KERN_EMERG "scdev_init!\n");
/*打印信息,KERN_EMERG表示紧急信息*/
return 0;
fail:
/*注销设备号*/
unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
printk(KERN_EMERG "kmalloc is fail!\n");
return ret;
}
static void scdev_exit(void)
{
int i;
printk(KERN_EMERG "scdev_exit!\n");
/*除去字符设备*/
for(i=0;i<DEVICE_MINOR_NUM;i++){
cdev_del(&(my_devices[i].cdev));
/*摧毁设备节点函数d*/
device_destroy(myclass,MKDEV(numdev_major,numdev_minor+i));
}
/*释放设备class*/
class_destroy(myclass);
/*释放内存*/
kfree(my_devices);
unregister_chrdev_region(MKDEV(numdev_major,numdev_minor),DEVICE_MINOR_NUM);
}
module_init(scdev_init);
/*初始化函数*/
module_exit(scdev_exit);
/*卸载函数*/
九、makefile
#!/bin/bash
#通知编译器我们要编译模块的哪些源码
#这里是编译itop4412_hello.c这个文件编译成中间文件mini_linux_module.o
obj-m += char_driver.o
#源码目录变量,这里用户需要根据实际情况选择路径
#作者是将Linux的源码拷贝到目录/home/topeet/android4.0下并解压的
KDIR := /work/itop-4412/iTop4412_Kernel_3.0
#当前目录变量
PWD ?= $(shell pwd)
#make命名默认寻找第一个目标
#make -C就是指调用执行的路径
#$(KDIR)Linux源码目录,作者这里指的是/home/topeet/android4.0/iTop4412_Kernel_3.0
#$(PWD)当前目录变量
#modules要执行的操作
all:
make -C $(KDIR) M=$(PWD) modules
#make clean执行的操作是删除后缀为o的文件
clean:
rm -rf *.mod.c *.o *.order *.ko *.mod.o *.symvers
十、运用程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
main(){
int fd;
char *hello_node0 = "/dev/chardevnode0";
char *hello_node1 = "/dev/chardevnode1";
/*O_RDWR只读打开,O_NDELAY非阻塞方式*/
if((fd = open(hello_node0,O_RDWR|O_NDELAY))<0){
printf("APP open %s failed!\n",hello_node0);
}
else{
printf("APP open %s success!\n",hello_node0);
}
close(fd);
if((fd = open(hello_node1,O_RDWR|O_NDELAY))<0){
printf("APP open %s failed!\n",hello_node1);
}
else{
printf("APP open %s success!\n",hello_node1);
}
close(fd);
}
十一、make
root@ubuntu16:/work/code/char_driver# make
make -C /work/itop-4412/iTop4412_Kernel_3.0 M=/work/code/char_driver modules
make[1]: Entering directory '/work/itop-4412/iTop4412_Kernel_3.0'
CC [M] /work/code/char_driver/char_driver.o
/work/code/char_driver/char_driver.c: In function 'chardevnode_ioctl':
/work/code/char_driver/char_driver.c:64: warning: format '%d' expects type 'int', but argument 3 has type 'long unsigned int'
Building modules, stage 2.
MODPOST 1 modules
CC /work/code/char_driver/char_driver.mod.o
LD [M] /work/code/char_driver/char_driver.ko
make[1]: Leaving directory '/work/itop-4412/iTop4412_Kernel_3.0'
root@ubuntu16:/work/code/char_driver#
十二、arm-none-linux-gnueabi-gcc -o invoke_char_driver invoke_char_driver.c -static
十三、加载char_driver.ko
[root@iTOP-4412]# insmod char_driver.ko
[15423.435619] numdev_major is 0!
[15423.437400] numdev_minor is 0!
[15423.440571] adev_region req 248 !
[15423.455196] cdev_add 0 is success!
[15423.463880] cdev_add 1 is success!
[15423.475650] scdev_init!
[root@iTOP-4412]#
十四、查看设备号
[root@iTOP-4412]# cat /proc/devices
Character devices:
1 mem
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
10 misc
........
248 chardevnode
248 就是生成的设备号
十五、查看设备节点
[root@iTOP-4412]# ls /dev/
AGPS input ppp tty3
HPD ion ptmx tty4
adc keychord pts ttyGS0
alarm kmem ram0 ttyGS1
android_adb kmsg ram1 ttyGS2
ashmem log ram10 ttyGS3
buzzer_ctl loop0 ram11 ttyS0
chardevnode0 loop1 ram12 ttyS1
chardevnode1 loop2 ram13 ttyS2
chardevnode0 chardevnode01 生成的两个设备节点
十六、查看生成的类
[root@iTOP-4412]# ls /sys/class/
android_usb gpsdrv kovaplus net scsi_device ump
arvo graphics lcd power_supply scsi_disk usb_device
backlight i2c-adapter lirc ppp scsi_generic video4linux
bdi i2c-dev mali pyra scsi_host
block ieee80211 mdio_bus rc sound
bluetooth input mem rc522 spi_master
chardevnode kone misc regulator switch
firmware koneplus mmc_host rtc tty
生成 chardevnode 类
十七、运行invoke_char_driver
[root@iTOP-4412]# ./invoke_char_driver
[15749.099091] chardevnode_open is success!
[15749.102116] chardevnode_release is success!
[15749.106236] chardevnode_open is success!
[15749.109748] chardevnode_release is success!
APP open /dev/chardevnode0 success!
APP open /dev/chardevnode1 success!
运行成功说明驱动注册完成