行业知识
Linux第67步
1、���符设备注册与注销的函数原型”
/*字符设备注册的函数原型*/
static inline int register_chrdev(unsigned int major,\
const char *name, \
const struct file_operations *fops)
/*
major:主设备号,Limnux下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分。
name:设备名字,指向一串字符串。
fops:结构体file_operations类型指针,指向设备的操作函数集合变量。
*/
/*字符设备注销的函数原型*/
static inline void unregister_chrdev(unsigned int major,\
const char *name)
/*
major:主设备号,Limnux下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分。
name:设备名字,指向一串字符串。
*/
2、Linux设备号
1)、使用设备号的原因:为了方便管理,Linux 中每个设备都有一个设备号。
2)、设备号的组成
Linux设备号是由主设备号和次设备号两部分组成;
主设备号表示某一个具体的驱动;
次设备号表示使用这个驱动的各个设备。
Linux使用“dev_t的数据类型”表示设备号;
“dev_t的数据类型”定义在文件“include/linux/types.h”里面,定义如下:
typedef __u32 __kernel_dev_t;
//为“__u32”起个别名叫“__kernel_dev_t”
typedef __kernel_dev_t dev_t;
//为“__kernel_dev_t”起个别名叫“dev_t”
由此可见,dev_t是 umsigned int类型,是一个32位的数据类型。这个32位的数据就是Linux设备号,它是由“主设备号”和“次设备号”两部分构成,其中高12位为“主设备号”,低20位为“次设备号”。因此,Linux系统中主设备号范围为0~4095,所以,大家在选择主设备号的时候一定不要超过这个范围。
在文件“include/linux/kdev_t.h”中提供了几个关于设备号的操作函数(本质是宏),如下所示:
#define MINORBITS 20 //定义“次设备号”占据低20位
#define MINORMASK ((1U > MINORBITS))
//输入参数dev为“Linux设备号”
//将dev右移20位得到“主设备号”
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
//输入参数dev为“Linux设备号”
//将dev与0xFFFFF相与后得到“次设备号”
#define MKDEV(ma,mi) (((ma)
p int i;/p p struct rtlx_info *info;/p p struct rtlx_info **p = vpe_get_shared(aprp_cpu_index());/p p /p p if (p == NULL || *p == NULL)/p p return;/p p /p p info = *p;/p p /p p if (info->ap_int_pending == 1 && smp_processor_id() == 0) {for (i = 0; i
wake_up(&channel_wqs[i].lx_queue);
wake_up(&channel_wqs[i].rt_queue);
}
info->ap_int_pending = 0;
}
}
/*从字面看是讲中断堆栈指针*/
void _interrupt_sp(void)
{
smp_send_reschedule(aprp_cpu_index());
}
/*入口函数初始化*/
int __init rtlx_module_init(void)
{
struct device *dev;
int i, err;
if (!cpu_has_mipsmt) {
pr_warn("VPE loader: not a MIPS MT capable processor\n");
return -ENODEV;
}
if (num_possible_cpus() - aprp_cpu_index()
pr_warn("No TCs reserved for AP/SP, not initializing RTLX.\n"
"Pass maxcpus= argument as kernel argument\n");
return -ENODEV;
}
major = register_chrdev(0, RTLX_MODULE_NAME, &rtlx_fops);
//注册字符设备驱动
//0为主设备号,采用宏RTLX_MODULE_NAME定义设备名字
//rtlx_fops是设备的操作函数集合,它是file_operations结构变量
if (major
pr_err("rtlx_module_init: unable to register device\n");
return major;
}
/* initialise the wait queues */
for (i = 0; i
init_waitqueue_head(&channel_wqs[i].rt_queue);
//初始化等待队列头
init_waitqueue_head(&channel_wqs[i].lx_queue);
//初始化等待队列头
atomic_set(&channel_wqs[i].in_open, 0);//状态重置
mutex_init(&channel_wqs[i].mutex);//初始化互斥体
dev = device_create(mt_class, NULL, MKDEV(major, i), NULL,
"%s%d", RTLX_MODULE_NAME, i);
//创建设备, major为主设备号,i为次设备号
//参数mt_class表示该设备位于mt_class类下面
// parent为NULL表示没有父设备
//将major左移20位,再与i相或,就得到“Linux设备号”
//drvdata为NULL表示没有使用设备文件
//采用RTLX_MODULE_NAME宏定义指向字符串表示设备名
if (IS_ERR(dev)) {
while (i--)
device_destroy(mt_class, MKDEV(major, i));
//删除创建的设备
//参数mt_class是设备所处的类,参数i是设备号
err = PTR_ERR(dev);
goto out_chrdev;
}
}
/* set up notifiers */
rtlx_notify.start = rtlx_starting;
rtlx_notify.stop = rtlx_stopping;
vpe_notify(aprp_cpu_index(), &rtlx_notify);
if (cpu_has_vint) {
aprp_hook = rtlx_interrupt;
} else {
pr_err("APRP RTLX init on non-vectored-interrupt processor\n");
err = -ENODEV;
goto out_class;
}
return 0;
out_class:
for (i = 0; i
device_destroy(mt_class, MKDEV(major, i));
//删除创建的设备
//参数mt_class是要删除的设备所处的类,参数i是要删除的设备号
out_chrdev:
unregister_chrdev(major, RTLX_MODULE_NAME);
//注销字符设备驱动
//major为主设备号,采用宏RTLX_MODULE_NAME定义设备
return err;
}
/*出口函数初始化*/
void __exit rtlx_module_exit(void)
{
int i;
for (i = 0; i
device_destroy(mt_class, MKDEV(major, i));
//删除创建的设备
//参数mt_class是要删除的设备所处的类,参数i是要删除的设备号
unregister_chrdev(major, RTLX_MODULE_NAME);
//注销字符设备驱动
//major为主设备号,采用宏RTLX_MODULE_NAME定义设备名字
aprp_hook = NULL;
}
5、编写“字符设备驱”动注册与注销的程序
1)、创建“/home/zgq/linux/Linux_Drivers/01_MyCharDevice/”目录
输入“cd /home/zgq/linux/Linux_Drivers/”
切换到“/home/zgq/linux/Linux_Drivers/”目录
输入“ls”,查询“/home/zgq/linux/Linux_Drivers/”目录下的文件和文件夹
输入“mkdir 01_MyCharDevice”
创建“/home/zgq/linux/Linux_Drivers/01_MyCharDevice/”目录
输入“ls”,查询“/home/zgq/linux/Linux_Drivers/”目录下的文件和文件夹
输入“cd 01_MyCharDevice/”
切换到“/home/zgq/linux/Linux_Drivers/01_MyCharDevice/”目录
输入“pwd”获取绝对路径
输入“cd /home/zgq/linux/Linux_Drivers/00_My_TestDriver/”
切换到“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录
输入“ls”,查询“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录下的文件和文件夹
输入“cp Makefile /home/zgq/linux/Linux_Drivers/01_MyCharDevice回车”
拷贝“Makefile”
2)、修改Makefile文件
Makefile文件内容如下:
KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31
#使用“:=”将其后面的字符串赋值给KERNELDIR
CURRENT_PATH := $(shell pwd)
#采用“shell pwd”获取当前打开的路径
#使用“$(变量名)”引用“变量的值”
obj-m := MyCharDevice.o
#生成“obj-m”需要依赖“MyCharDevice.o”
build: kernel_modules
#生成“build”需要依赖“kernel_modules”
@echo $(KERNELDIR)
#输出KERNELDIR的值为“/home/zgq/linux/atk-mp1/linux/linux-5.4.31”
@echo $(CURRENT_PATH)
#输出CURRENT_PATH的值为/home/zgq/linux/Linux_Drivers/00_My_TestDriver”
@echo $(MAKE)
#输出MAKE的值为make
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
#后面的"modules"表示编译成模块
#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”
#“CURRENT_PATH”上面定义为“当前的工作目录”
#“-C $(KERNELDIR) M=$(CURRENT_PATH) ”表示将“当前的工作目录”切换到“指定的目录”中
#即切换到“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”。
#M表示模块源码目录
#在“make和modules”之间加入“M=$(CURRENT_PATH)”,表示切换到由“CURRENT_PATH”指定的目录中读取源码,同时将其编>译为.ko 文件
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”
#“CURRENT_PATH”上面定义为“当前的工作目录”
3)、使用VSCode创建“MyCharDevice.c”文件
添加内容如下:
#include //必须要包含的头文件
#include //必须要包含的头文件
#include //下面要用到字符串,显然也要包含
#include //必须要包含的头文件
#include //必须要包含的头文件
#include //使能结构体"file_operations"
#define MyCharDevice_MAJOR 200
//定义主设备号
//可以通过串口输入“cat /proc/devices”查询当前已用的主设备号
#define MyCharDevice_NAME "MyCharDeviceName"
//使用MyCharDevice_NAME指向一串字符串表示设备的名字。
const struct file_operations MyCharDevice_fops = {};
//声明file_operations结构变量MyCharDevice_fops
//它是指向设备的操作函数集合变量
/*入口函数初始化*/
static int __init MyCharDevice_init(void)
{
int ret = 0;
printk("MyCharDevice_init\r\n");
ret = register_chrdev(MyCharDevice_MAJOR, MyCharDevice_NAME, &MyCharDevice_fops);
//返回的ret=0表示字符设备驱动注册成功
//主设备号为MyCharDevice_MAJOR
//设备名字为RTLX_MODULE_NAME宏定义
//MyCharDevice_fops是设备的操作函数集合
if (ret
pr_err("MyCharDevice_init is failed!!!\r\n");
return ret;
}
else printk("ret=%d\r\n",ret);
return ret;
}
/*出口函数初始化*/
static void __exit MyCharDevice_exit(void)
{
printk("MyCharDevice_exit\r\n");
unregister_chrdev(MyCharDevice_MAJOR, MyCharDevice_NAME);
//主设备号为MyCharDevice_MAJOR的值
//设备名字为MyCharDevice_NAME宏定义的字符串“MyCharDeviceName”
}
module_init(MyCharDevice_init);
/*将MyCharDevice_init()指定为入口函数*/
module_exit(MyCharDevice_exit);
/*将MyCharDevice_exit()指定为出口函数*/
MODULE_AUTHOR("Zhanggong");//添加作者名字
MODULE_DESCRIPTION("This is test module!");//模块介绍
MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”
MODULE_INFO(intree,"Y");
//去除显示“loading out-of-tree module taints kernel.”
4)、编译
输入“cd /home/zgq/linux/Linux_Drivers/01_MyCharDevice/”
输入“ls”
输入“sudo cp MyCharDevice.ko /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31”
输入密码“123456回车”
输入“cd /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31”
输入“ls”
5)、测试
启动开发板,从网络下载程序
输入“root”
输入“cd /lib/modules/5.4.31/”
在nfs挂载中,切换到“/lib/modules/5.4.31/”目录,
注意:“lib/modules/5.4.31/”在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中。
输入“ls”
输入“depmod”,驱动在第一次执行时,需要运行“depmod”
输入“modprobe MyCharDevice.ko”,加载“MyCharDevice.ko”模块
输入“lsmod”查看有哪些驱动在工作
输入“rmmod MyCharDevice.ko”,卸载“MyCharDevice.ko”模块
注意:输入“rmmod MyCharDevice”也可以卸载“MyCharDevice.ko”模块
输入“lsmod”查看有哪些驱动在工作
输入“modprobe MyCharDevice.ko”,加载“MyCharDevice.ko”模块
输入“cat /proc/devices回车”查询设备号
下一篇:怎么玩韩国服务器lol?