概述
系列文章目录
第一章 Linux 中内核与驱动程序
第二章 Linux 设备驱动编写 (misc)
第三章 Linux 设备驱动编写及设备节点自动生成 (cdev)
第四章 Linux 平台总线platform与设备树
第五章 Linux 设备树中pinctrl与gpio(lichee nano pi)
文章目录
- 系列文章目录
- 前言
- 一、Helloworld
- 1.在Ubuntu上加载驱动
- 2.在开发板上加载驱动
- 二、杂项设备驱动(重头戏,有一定基础可以直接看这里)
- 1.misc设备驱动编写思路
- 2.用misc设备驱动点亮小灯
- 3.测试程序编写
- 附录:杂项设备驱动(Ubuntu上运行的)
前言
Linux设备驱动有很多种,整体分成三大类:字符设备设备驱动、块设备驱动和网络设备。其中前两者的区别是传输数据的单元大小,一个传输字符,一个传输块,网络设备是根据socket。除了上述三种之外还有一种叫做杂项设备驱动(B站迅为电子视频的叫法),所有的杂项设备(misc)的主设备号为10,而且它会自动生成设备节点,具体的过程,本文将详细展开。
一、Helloworld
路要一步步走,首先我们来看一下驱动程序中的hello world。
下面给出一个例子,有两个文件:hello.c和Makefile。这两个文件在一个目录下,通过make命令可以直接生成hello.ko命令,再使用insmod命令就可以安装驱动到内核。
hello.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello word");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye,Hello word");
}
module_init( hello_init );
module_exit( hello_exit );
可以看出驱动程序的结构和我们一般编写的应用程序(比如第一个打印hello world的程序)不太一样。它里面没有main文件,那么他怎么运行呢?这也是驱动程序和应用程序的区别。在O’Reilly 的动物系列丛书中有本《Linux设备驱动程序》提到过这样一个概念(大概):驱动程序提供的是“机制”,应用程序提供的是“策略”。举个例子:机制就是游戏手柄上的上、下、左、右四个按钮对应人物的走动,策略就是你打游戏时什么自由的控制任务走动。
驱动程序有两个函数:module_init( )和module_exit( )。当在终端使用insmod加载模块时,会执行module_init( )内的程序去完成相应的注册;用rmmod命令卸载模块时,执行module_exit( )中的程序,将一些资源释放。加载好模块之后可以使用lsmod命令查看已加载模块,模块的名字就是你生成的ko文件的名字。
上述编写好了hello.c文件那么接下来需要编译成hello.ko文件,然后加载到内核当中去。
在此之前要先明确一个问题:Ubuntu上跑的程序是x86架构的,开发板上跑的是arm架构的。为此在这里分两个标题讲。注:在终端通过【file 文件名】的格式可以查看文件是哪一种。
1.在Ubuntu上加载驱动
编写了如下适配于Ubuntu的Makefile文件,讲它和hello.c文件放在一个目录下,在该目录打开终端make就可以,这里默认环境就是x86环境。
Makefile (Ubuntu版本-x86架构)
ifneq ($(KERNELRELEASE),)
obj-m := hello.o // 要和.c文件同名字
else
KDIR:=/lib/modules/$(shell uname -r)/build //Ubuntu内核源码的路径
PWD:=$(shell pwd)//当前路径
all:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules //去内核源码中编译
endif
clean:
rm -f *.o *.ko *.mod.c .hello*
make之后会生产hello.ko文件,然后依次运行以下命令
insmod hello.ko
ls hello
下面是我insmod hello.ko之后的,可以看到该模块并没有什么用处,毕竟是第一个hello world。而且由于是虚拟机,并不会打印出我们在注册时printk的消息,可以通过dmesg命令查看内核日志。
rmmod hello
2.在开发板上加载驱动
上述用的是Ubuntu的内核去编译的模块,所以能在Ubuntu上加载驱动。所以要想在开发板上加载驱动,需要开发板的内核。这里就要求你在开发板上烧写的内核,你的Ubuntu上也有有一套源码。
以我的开发板为例,我用的是荔枝nano开发板。用的内核是4.14.0-licheepi-nano,是一个主线Linux内核。
这里的环境不再是默认的x86架构,需要更改成适合arm架构的环境。
//更改arm环境
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf- //这里具体看你哪里用的是什么交叉编译器
更环境变量之后,还需要将内核编译一下。注:这是在内核文件里make,也是需要arm环境的。这里如果不编译内核时会报错的,我这里报错:*** 没有规则可制作目标“modules”。
make -j12 //开12线程,会快一些
上述内核改好之后在你写驱动的文件夹里,也设置成arm环境,利用下面的Makefile去make。
Makefile (开发板版本-arm架构)适配于我的内核的Makefile文件
obj-m := hello.o
KDIR:=/home/ice/workspace/linuxtf //linuxtf是我的开发板的内核源码
PWD:=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.o *.ko *.mod.* Module.* modules.*
用file命令查看该文件的类型,是ARM的。
完事之后会生成hello.ko文件,你可以通过tftp或者nfs等方式将该文件传输到开发板。
用这些命令:insmod 、rmmod、lsmod去操作。
二、杂项设备驱动(重头戏,有一定基础可以直接看这里)
路要一步步走。上述说了Ubuntu上跑的程序是x86架构的,开发板上跑的是arm架构的,并且讲了怎么去设置两种环境。下面来看杂项设备驱动(misc_device)
杂项设备驱动是一种比字符设备要简单的驱动类型,一些简单的点灯程序也是完全够用。通过cat /proc/misc可以看到内核中的杂项设备号。注意这里的设备号和名字并不是设备文件(节点),设备节点在 /dev目录下。
1.misc设备驱动编写思路
杂项设备的编写不再是Helloworld那样简单了只能注册、注销。现在的目标是点灯。注:具体的一些基本概念请看上一节的内容,在此不作过多的赘述。
注册思路:
1注册杂项设备<=2构建杂项设备结构体
3构架file_operations结构体
4卸载杂项设备
这么说可能不太还理解,我来解释一下:我们要写一个驱动,当然要注册了,但是要注册什么呢?这就需要杂项设备结构体,注册了之后还要有一步就是通过file_operations结构体链接应用层和驱动层。最后是卸载驱动。整体思路就是这样。
1 、extern int misc_register(struct miscdevice *misc);注册杂项设备
extern int misc_deregister(struct miscdevice *misc);注销杂项设备
2 、杂项设备结构体如下图所示:
minor 代表次设备号;
name 代表设备节点的名字;
&fops 代表与之相关的file_operations结构体。只写这三个就够了。
3 、file_operations结构体的定义
小结所以实际完成驱动的编写的步骤是(1)填充miscdevice这个结构体(2)填充file_operations这个结构体(3)注册杂项设备并生生成设备节点。
2.用misc设备驱动点亮小灯
现在来给它加上点灯的功能,这里就提一句:由于Linux内核中不允许直接访问实际物理地址,需要在内核中用ioremap函数将实际地址转换成虚拟地址。具体你要点亮哪一个引脚上的LED看自己开发板的引脚的地址。
misc_led.c //将open、release、write、read补充上就行。
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/miscdevice.h> /* misc... */
#include <linux/fs.h> /* everything... */
#include <linux/types.h> /* size_t */
#include <linux/uaccess.h>
#include <linux/io.h>
#define GPIO 0x01c20800 //找你自己的
unsigned int *vir_gpio;
int misc_open(struct inode *inode,struct file *file)
{
printk( "open my_misc_devn");
return 0;
}
int misc_release(struct inode *inode,struct file *file)
{
printk( "release my_misc_devn");
return 0;
}
ssize_t misc_read(struct file *file,char __user *ubuf,size_t size,loff_t *loff_t)
{
char kbuf[64] = "xieshangle";
if(copy_to_user(ubuf,kbuf,strlen(kbuf))!= 0)
{
printk( "copy_to_user errorn");
return -1;
}
printk( "misc_readn");
return 0;
}
ssize_t misc_write(struct file *file,const char __user *ubuf,size_t size,loff_t *loff_t)
{
char kbuf[64] = {0};
if(copy_from_user(kbuf,ubuf,size)!= 0)
{
printk( "copy_from_user errorn");
return -1;
}
if(kbuf[0] == 1) //当用户对设备进行写操作时,将寄存器地址置位,
*vir_gpio |=(1 << 1);//具体高位亮还是地位亮看自己的开发板电路
else if(kbuf[0] == 0)
*vir_gpio &=~(1 << 1);
return 0;
}
struct file_operations misc_fops={
.owner =THIS_MODULE,
.open =misc_open,
.release =misc_release,
.read =misc_read,
.write = misc_write,
};
struct miscdevice misc_dev ={
.minor =MISC_DYNAMIC_MINOR,
.name ="my_misc",
.fops =&misc_fops,
};
static int misc_init(void)
{
int ret;
ret =misc_register(&misc_dev);
if(ret < 0){
printk( "misc register is failedn");
return -1;
}
printk( "misc register is succeedn");
vir_gpio = ioremap(GPIO,4);
if(vir_gpio == NULL)
{
printk( "ioremap is failedn");
return -EBUSY;
}
printk( "GPIO is ioremap to vir_gpion");
return 0;
}
static void misc_exit(void)
{
misc_deregister(&misc_dev);
iounmap(vir_gpio);
printk(KERN_ALERT "Goodbye,miscn");
}
/* register the init and exit routine of the module */
module_init( misc_init );
module_exit( misc_exit );
MODULE_LICENSE("GPL");
make之后加载到开发板,insmod之后再来编写一个测试程序。
3.测试程序编写
app.c //
./app 1
./app 0
送入什么将对应引脚置为对应电平
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd;
char buf[64] = {0};
fd = open("/dev/my_misc",O_RDWR);
if(fd < 0)
{
perror("open errorn");
return fd;
}
//read(fd,buf,sizeof(buf));//
//printf("buf is %sn",buf);//
buf[0] = atoi(argv[1]);
write(fd,buf,sizeof(buf));
if(buf[0] == 1)
printf("gao dian ping");
else if(buf[0] == 0)
printf("di dian ping");
close(fd);
return 0;
}
这里注意对应用程序进行编译用的·和环境的交叉编译器有一点不一样。
arm-linux-gnueabi-gcc app.c -o app -static
会生成arm架构的app可执行文件。
app编译好之后也传输到开发板:
由于荔枝nano单板没有led灯,我在此将信息打印出来了。
附录:杂项设备驱动(Ubuntu上运行的)
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/miscdevice.h> /* misc... */
#include <linux/fs.h> /* everything... */
struct file_operations misc_fops={
.owner=THIS_MODULE
};
struct miscdevice misc_dev ={
.minor=MISC_DYNAMIC_MINOR,
.name="my_misc",
.fops=&misc_fops,
};
static int misc_init(void)
{
int ret;
ret =misc_register(&misc_dev);
if(ret < 0){
printk( "misc register is failedn");//注册失败打印失败
return -1;
}
printk( "misc register is succeedn");//注册成功打印成功
return 0;
}
static void misc_exit(void)
{
misc_deregister(&misc_dev);
printk(KERN_ALERT "Goodbye,miscn");
}
/* register the init and exit routine of the module */
module_init( misc_init );
module_exit( misc_exit );
MODULE_LICENSE("GPL");
现在Ubuntu上加载一下模块,insmod再rmmod之后,由于是虚拟机不能直接显示printk的信息,利用dmesg可以查看内核日志。
最后
以上就是无限世界为你收集整理的Linux 设备驱动编写(misc)系列文章目录前言一、Helloworld二、杂项设备驱动(重头戏,有一定基础可以直接看这里)附录:杂项设备驱动(Ubuntu上运行的)的全部内容,希望文章能够帮你解决Linux 设备驱动编写(misc)系列文章目录前言一、Helloworld二、杂项设备驱动(重头戏,有一定基础可以直接看这里)附录:杂项设备驱动(Ubuntu上运行的)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复