在学习字符驱动模型之前,一些关于环境搭建,与最基本的linux驱动框架建议参考这篇文章:
环境搭建、linux驱动基本框架
以下主要包括字符驱动和上位机程序部分
(一)字符驱动模型
关于字符驱动模型,简单提点一下,以实战为主。
驱动原理图:
从结构上来看,字符驱动分为两部分:
第一部分,标准的驱动入口
有一般linux驱动的标准特点,整体入口在module_init、module_exit:
static int memdev_init(void){
int ret = -1;
/*动态分配设备号*/
ret = alloc_chrdev_region(&my_dev.devno,0,1,"my_led_memdev");
if (ret >= 0){
cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/
cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/
printk(KERN_ALERT "init_success
");
}
else
{
printk(KERN_ALERT "init_error
");
}
return ret;
}
static void memdev_exit(void){
cdev_del(&my_dev.cdev);
unregister_chrdev_region(my_dev.devno,1);
printk(KERN_ALERT "exit_my_led
");
}
module_init(memdev_init);
module_exit(memdev_exit);
以上以module_init、module_exit作为入口,把memdev_init等两个函数分别传进去作为真实调用的初始化函数。关于内部一些添加设备和动态、静态分配设备号的方法及其原理不再赘述。
第二部分,设备描述符、设备号
设备描述符:struct cdev
设备号:dev_t
都需要单独定义。
设备描述符struct cdev和设备号dev_t在第一部分中的设备初始化函数里面使用,用于添加字符设备:
cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/
实际上设备号是结构体struct cdev(设备描述符)内的一个成员,外部定义的设备号在alloc_chrdev_region函数里面动态分配后,再通过cden_add传回cdev内部那个设备号里面。差不多有一些中间量的意思。
此外还需要定义、开辟一个全局内存。例程里面把他们全都放进一个结构体里面:
#define MEM_SIZE 1024
struct mem_dev{
struct cdev cdev;
int mem[MEM_SIZE];//全局内存4k
dev_t devno;
};
struct mem_dev my_dev;
第三部分,操作函数集——file_operations结构体
简单的操作函数集定义:
const struct file_operations mem_ops = {
.llseek = mem_llseek,
.open = mem_open,
.read = mem_read,
.write = mem_write,
.release = mem_release,
};
当然,完整的操作函数集可不止这么一点,但初学入门这点就足够了。
关于操作函数集,这里需要提一个概念,字符驱动他的机制是这样的:应用程序、设备驱动程序之间,是隔着一个字符设备文件的,你使用read、write这些函数对下位进行各种协议(uart等)通讯时,你读的是这个
字符设备文件,一般在/dev/xxx这里。但是内核接收到你这个read、write函数会自动调用操作函数集里面指向的函数,比如上面这个就是指向men_write。
应用程序——>字符设备文件——>设备驱动程序
open:打开文件
release:关闭文件
read:读文件
write:写文件
llseek:移动读写文件指针
这部分剩下的就是对各个文件操作函数进行实例化编写。
第二部分、第三部分耦合
显然,第二部分和第三部分被明显区分开来了,目前来说没有发现耦合关系,那么实际上他么是怎么进行耦合的呢?
可以在模块的初始化memdev_init函数处看到:
cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/
cdev_init函数把他们耦合起来了!
编译
对于Ubuntu18.04 LTS来说,默认已经有modules了,不需要额外搭建环境,makefile如下:
obj-m:=my_led.o
KERNELDIR:=/lib/modules/4.15.0-45-generic/build
PWD:=$(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.orde
安装模块
sudo insmod my_led.ko
lsmod #查看是否加载好
cat /proc/devices #查看自动分配的号码
sudo mknod /dev/my_led_memdev0 c 243 0 #其中243是上面自动分配的号码
控制台通常是打印不出信息,要从这里查看系统log:
sudo tail /var/log/syslog
卸载模块
sudo rm /dev/my_led_memdev0
sudo rmmod my_led
一些问题
在Ubuntu18.04 LTS下面编译其内核对应的字符驱动,会出现copy_to_user和copy_from_user两个函数的错误提示,但在交叉编译nanopc t4的驱动程序并没有。
对于Ubuntu18.04 LTS我把copy_to_user和copy_from_user改成了raw_copy_to_user和raw_copy_from_user,运行正常。看样子是这个内核把这两个函数改了?
(二)应用程序
一开始我在网络上copy了一份例程,略作修改,但无论怎么运行都没办法达到预期期望:
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd=0;
int val=1;
char buf_write[5]="only";
char buf_my[10]={0};
fd=open("/dev/my_led_memdev0",O_RDWR);
if(fd<0)
printf("can't open!
");
else
{
write(fd, buf_write, 4);
read(fd, buf_my, 4);
printf("%s", buf_my);
}
printf("end
");
return 0;
}
后来仔细探索,发现write和read的读写,是从文件指针loff_t开始的,write调用以后,loff_t文件指针就指向了最后一位,直接读取是没办法读到刚才写进去的数据。
我不知道这份例程的作者有没有运行过他的代码,甚至很多例程都是这样写的。我不清楚是不是驱动函数的策略不同或者是内核不一样的缘故,但其中绝对有不少实在误人子弟!
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd=0;
int val=1;
char buf_write[5]="wond";
char buf_my[10]={0};
fd=open("/dev/my_led_memdev0",O_RDWR);
if(fd<0)
printf("can't open!
");
else
{
lseek(fd, 0, SEEK_SET);
write(fd, buf_write, 4);
lseek(fd, 0, SEEK_SET);
read(fd, buf_my, 8);
printf("read:%s
", buf_my);
}
printf("end
");
return 0;
}
附上驱动代码:
#include
#include
#include
#include
#include
#define MEM_SIZE 1024
MODULE_LICENSE("GPL");
struct mem_dev{
struct cdev cdev;
int mem[MEM_SIZE];//全局内存4k
dev_t devno;
};
struct mem_dev my_dev;
/*打开设备*/
int mem_open(struct inode *inode, struct file *filp){
int num = MINOR(inode->i_rdev);/*获取次设备号*/
printk(KERN_ALERT "open:my_led_memdev
");
if(num == 0){/*判断为那个设备*/
filp -> private_data = my_dev.mem;/*将设备结构体指针复制给文件私有数据指针*/
}
return 0;
}
/*文件关闭函数*/
int mem_release(struct inode *inode, struct file *filp){
return 0;
printk(KERN_ALERT "close:my_led_memdev
");
}
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){
int * pbase = filp -> private_data;/*获取数据地址*/
unsigned long p = *ppos;/*读的偏移*/
unsigned int count = size;/*读数据的大小*/
int ret = 0;
printk(KERN_ALERT "read:ready to my_led_memdev
");
if(p >= MEM_SIZE)/*合法性判断*/
return 0;
if(count > MEM_SIZE - p)/*读取大小修正*/
count = MEM_SIZE - p;
if(raw_copy_to_user(buf,pbase + p,size)){
ret = - EFAULT;
}else{
*ppos += count;
ret = count;
}
printk(KERN_ALERT "read:my_led_memdev
");
return ret;
}
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos){
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
int *pbase = filp -> private_data;
if(p >= MEM_SIZE)
return 0;
if(count > MEM_SIZE - p)
count = MEM_SIZE - p;
if(raw_copy_from_user(pbase + p,buf,count)){
ret = - EFAULT;
}else{
*ppos += count;
ret = count;
}
printk(KERN_ALERT "write:my_led_memdev
");
return ret;
}
/*seek文件定位函数*/
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence){
loff_t newpos;
switch(whence) {
case SEEK_SET:/*从文件头开始定位*/
newpos = offset;
break;
case SEEK_CUR:/*从当前位置开始定位*/
newpos = filp->f_pos + offset;
break;
case SEEK_END:
newpos = MEM_SIZE * sizeof(int)-1 + offset;/*从文件尾开始定位*/
break;
default:
return -EINVAL;
}
if ((newpos<0) || (newpos>MEM_SIZE * sizeof(int)))/*检查文件指针移动后位置是否正确*/
return -EINVAL;
printk(KERN_ALERT "llseek:my_led_memdev
");
filp->f_pos = newpos;
return newpos;
}
const struct file_operations mem_ops = {
.llseek = mem_llseek,
.open = mem_open,
.read = mem_read,
.write = mem_write,
.release = mem_release,
};
static int memdev_init(void){
int ret = -1;
/*动态分配设备号*/
ret = alloc_chrdev_region(&my_dev.devno,0,1,"my_led_memdev");
if (ret >= 0){
cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/
cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/
printk(KERN_ALERT "init_success
");
}
else
{
printk(KERN_ALERT "init_error
");
}
return ret;
}
static void memdev_exit(void){
cdev_del(&my_dev.cdev);
unregister_chrdev_region(my_dev.devno,1);
printk(KERN_ALERT "exit_my_led
");
}
module_init(memdev_init);
module_exit(memdev_exit);
好好学习,天天向上