1. 生成设备节点
1.1 杂项设备
所有的设备都有自己的设备号,我们可以利用命令:
cat /proc/devices
来观察设备号:
一共255个设备号,这些都是主设备号,这显然不够用的。所以很多驱动都挂载在杂线设备的下面,也就是设备号为10的misc下面,这样可以节省主设备号,而且这样可以减少一步注册主设备号的过程。
1.2 注册文件
杂项设备的初始化部分源文件“drivers/char/misc.c”,里面内容就是给字符驱动做一个简单的封装。注册文件在:
/LinuxKernelPath/include/linux/miscdevice.h
中,结构体miscdevice以及注册函数如下所示:
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
extern int misc_register(struct miscdevice * misc);
extern int misc_deregister(struct miscdevice *misc);
其中我们要用到的参数为:
.minor:设备号
.name:生成设备节点的名称
.fops:指向一个设备节点文件
1.3 生成设备节点源代码
生成设备节点的例程在Linux驱动(10)中的基础上添加的:
/*
Name:Device_Node.c
Author:Ethan
Version:1.0
Date:2019-11-30
*/
/*包含初始化宏定义的头文件,代码中的module_init和module_exit再次文件中)*/
#include<linux/init.h>
/*包含初始化加载模块的头文件,代码中的MODULE_LICENSE在此头文件中*/
#include<linux/module.h>
/*驱动注册的头文件,包含驱动的结构体以及注册和卸载的函数*/
#include<linux/platform_device.h>
/*注册杂项设备头文件*/
#include <linux/miscdevice.h>
/*注册设备节点的文件结构体*/
#include <linux/fs.h>
/*声明驱动名称*/
#define DRIVER_NAME "helloworld_ctl"
/*声明设备节点名称*/
#define DEVICE_NAME "helloworld_node_ctl"
/*声明是开源的,没有内核版本限制*/
MODULE_LICENSE("Dual BSD/GPL");
/*声明作者*/
MODULE_AUTHOR("Ethan");
/*定义文档打开函数*/
static int device_node_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "Hello open! \n");
return 0;
}
/*定义文档关闭函数*/
static int device_node_release(struct inode *inode, struct file *file){
printk(KERN_EMERG "Hello release! \n");
return 0;
}
/*定义IO控制函数*/
static long device_node_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
printk("cmd is %d, arg is %d \n",cmd,arg);
return 0;
}
/*定义文件操作结构体*/
struct file_operations ops_device_node = {
.owner = THIS_MODULE,
.open = device_node_open,
.release = device_node_release,
.unlocked_ioctl = device_node_ioctl,
};
/*定义杂项设备节点结构体*/
struct miscdevice misc_device_node = {
/*自动分配设备号*/
.minor = MISC_DYNAMIC_MINOR,
/*生成设备节点名称*/
.name = DEVICE_NAME,
/*指向一个设备节点文件*/
.fops= &ops_device_node,
};
/*定义驱动的probe函数*/
static int hello_probe(struct platform_device *pdv){
/*打印信息,KERN_KMERG表示紧急信息*/
printk(KERN_EMERG "Hello probe! \n");
/*注册杂项设备*/
misc_register(&misc_device_node);
return 0;
}
/*定义驱动的remove函数*/
static int hello_remove(struct platform_device *pdv){
/*打印信息,KERN_KMERG表示紧急信息*/
printk(KERN_EMERG "Hello remove! \n");
/*移除杂项设备*/
misc_deregister(&misc_device_node);
return 0;
}
/*定义驱动的shutdown函数*/
static void hello_shutdown(struct platform_device *pdv){
/*打印信息,KERN_KMERG表示紧急信息*/
printk(KERN_EMERG "Hello shutdown! \n");
}
/*定义驱动的suspend函数*/
static int hello_suspend(struct platform_device *pdv){
/*打印信息,KERN_KMERG表示紧急信息*/
printk(KERN_EMERG "Hello suspend! \n");
return 0;
}
/*定义驱动的resume函数*/
static int hello_resume(struct platform_device *pdv){
/*打印信息,KERN_KMERG表示紧急信息*/
printk(KERN_EMERG "Hello resume! \n");
return 0;
}
/*定义驱动结构体*/
struct platform_driver helloworld_driver = {
.probe = hello_probe,
.remove = hello_remove,
.shutdown = hello_shutdown,
.suspend = hello_suspend,
.resume = hello_resume,
.driver ={
.name = DRIVER_NAME,
.owner = THIS_MODULE,
}
};
/*编写初始化函数*/
static int hello_init(void)
{
/*定义驱动状态变量*/
int driverState;
/*打印hello world,KERN_KMERG表示紧急信息*/
printk(KERN_EMERG "Hello World! \n");
/*注册helloworld驱动*/
driverState = platform_driver_register(&helloworld_driver);
/*打印驱动注册状态,KERN_KMERG表示紧急信息*/
printk(KERN_EMERG "driverState is %d! \n",driverState);
return 0;
}
//编写卸载函数
static void hello_exit(void)
{
/*打印see you,KERN_KMERG表示紧急信息*/
printk(KERN_EMERG "See you!\n");
/*卸载helloworld驱动*/
platform_driver_unregister(&helloworld_driver);
}
/*初始化函数*/
module_init(hello_init);
/*卸载函数*/
module_exit(hello_exit);
1.4 生成设备节点步骤
注册设备的步骤为下:
① 将Device_Node.c和Makefile文件移动到linux系统中的某一文件夹下(任意地方或者新建都可以)。
② 执行命令:
make
执行完毕之后可以在该文件夹下生成.ko文件,该文件就是编译生成的驱动。
③ 我们通过开发板来验证该驱动是否编写与编译成功:
—>将.ko文件拷贝到U盘中
—>用命令
mount /dev/sda1 /mnt/disk/
将U盘挂载。
—>可以用命令
ls /mnt/disk/
来列出已经编译成功的.ko文件
—>用加载命令,来加载我们编译好的驱动
insmod /mnt/disk/Device_Module.ko
结果为:
然我们使用命令:
ls /dev
观察我们生成的设备节点为:
1.5 需要注意的问题
设备节点是跟着驱动注册一起生成的,它放在驱动注册的prob函数中,切记,只有当驱动与设备名称一致时,设备节点才能注册成功!
2. 调用设备节点
调用设备节点就是将我们刚注册的设备节点helloworld_node_ctl当做文件来读取,源文件如下所示:
/*
Name:Device_Node_calls.c
Author:Ethan
Version:1.0
Date:2019-11-30
*/
/*调用打印函数printf*/
#include <stdio.h>
/*基本系统数据类型*/
#include <sys/types.h>
/*系统调用函数头文件,可以调用普通文件、目录、管道、socket、字符、块的属性*/
#include <sys/stat.h>
/*定义了open函数*/
#include <fcntl.h>
/*定义了close函数*/
#include <unistd.h>
/*定义了ioctl函数*/
#include <sys/ioctl.h>
main(){
/*定义文件句柄*/
int fd;
/*定义了设备节点的路径*/
char *hello_node = "/dev/helloworld_node_ctl";
/*O_RDWR只读打开,O_NDELAY非阻塞方式*/
if((fd = open(hello_node,O_RDWR|O_NDELAY))<0){
printf("APP open %s failed",hello_node);
}
else{
printf("APP open %s success",hello_node);
/*向节点传递参数*/
ioctl(fd,1,6);
}
close(fd);
}
调用设备节点的步骤请参考之前的博文:Linux基础(3)–搭建最小的Linux系统