这是博主第一次写开放式技术性文章,写这篇文章的目标既是为了总结学到的常识,也是为了能帮忙老手,因为在学习的过程中也是面向某度编程,导致呈现了很多的坑,既有相干帖子的问题,也有本人的问题,我会将其中的坑一一注明,尽可能地让大家在少踩坑的过程中写出一个驱动。
一、一个要解决加减法问题的驱动
题目:
通过内核驱动程序,在安卓终端实现加减运算
要求:a. 算法在内核驱动中实现,通过ioctl调用;
b. 应用程序调用驱动算法实现加减法运算,在安卓终端输出
./fun 3 + 5,输入后果8;
要解决这个问题,咱们就要理解linux驱动的相干常识、编译工具的应用、安卓调试工具ADB的应用、linux驱动调试办法以及Makefile的编写等常识,重在利用怎么调用驱动这个过程。
二、基础知识大梳理
- linux驱动常识
(1)linux驱动分类
linux中所有皆文件,linux零碎把硬件设施标记为非凡的文件,分为以下三种:
字符设施就是LCD、触摸屏等设施,块设施就是如U盘、SD卡、电脑硬盘等设施,而网络设备就是常见的网卡设施、蓝牙设施等。依据题目要求,这次的驱动就以一个简略的字符驱动开始。
(2)利用怎么调用驱动
!
两幅图都援用自两位前辈的帖子,再次感激前浪的开源分享精力!先是草根老师的图,比拟具体地介绍了应用程序怎么通过形象函数open()、read()、wiite()、ioctl()函数实现关上文件获取文件描述符、寻找对应inode,继而创立struct file,保留文件信息,确认设施信息,拷贝cdev地址信息,进而通过这些信息找到咱们所写的底层write()、ioctl()函数,实现相应的性能,数据从应用层、VFS虚构文件系统再到驱动层再最终传达到硬件设施,通过比照crystal_guang老师第二幅图,能够理解VFS层的实现根底就是库以及内核。
2.设么是内核态?什么是用户态
图片来自于在此期间的笔记,曾经很充沛了,咱们这次次要的工作就是实现用户态到内核态的切换,从切换形式可知咱们是通过零碎调用来应用驱动的。
- 怎么写
仍然拿出草根老师的图:
驱动的实现具体就是三部,分为底层(本人写的)open()、write()、read()、ioctl()函数的实现,struct file_operations、初始化函数以及退出函数,具体程序放到前面。
实现驱动计算得出后果有2种方法:
(1)、计算后用return返回计算值,间接输入后果
(2)、开拓空间保留,而后用read()读取
既然要玩,果然要谋求刺激,咱们抉择第二种办法。我的程序参考了草根老师的驱动程序,程序写的十分丑陋,而且草根老师程序足足更改了三次,欠缺了很多,最初的版本能够驱动多个同类设施并且能够动静申请节点,然而帖子有些内容不全面而且也有问题,这也是我写这篇文章的初衷,那就是写一个真真能够调用的驱动而不是输入hello,我会在最初把他们的博客内容放在最初,通过浏览我的以及他们的博客,置信前人能够很顺利写出一个能够跑的字符驱动。
- 来聊聊ioctl()
ioctl函数是文件构造中的一个属性重量,就是说如果你的驱动程序提供了对ioctl的反对,用户就能够在用户程序中应用ioctl函数来管制设施的I/O通道,它须要传递三个参数,蕴含文件动静信息的文件构造体,命令以及参数。第一个在应用程序里反映的就是文件描述符,cmd则是判断语句里须要用到的,linux为cmd设计了具体的规定以及命令生成函数以便于程序人员开发本人的命令,第三个参数就用来传递参数,既能够传递整数也能够传递指针,然而指针的传递须要在程序外面进行测验,否则有可能造成零碎奔溃。CMD的常识须要大家本人学习一下,命令的规定比较复杂,相对来说网上材料也比拟多,大家齐全能够本人学习一下,而且ioctl()函数在 2.6当前的内核中改版了,须要留神一下。
5.什么是编译?什么是穿插编译?什么是连贯
因为大家很多都是从IDE时代过去的,对编译的概念很含糊,然而到linux环境中必须要理解这些常识。
6.Makefile、.ko文件
Makefile文件用来让大家实现主动编译,从而生成驱动文件.ko,具体的语法以及常识,大家能够本人去学习一下,这些也是一名嵌入式工程师的必修课。上面贴一下我的Makefile
KERN_DIR = /studio/...all: make -C $(KERN_DIR) M=`pwd` modules CROSS_COMPILE=aarch64-linux-gnu-clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.orderobj-m += in_proc.o
其中KERN_DIR就是你所用linux零碎kernel内核的门路,须要提一下的是,在make命令之前须要先编译一下内核。
7.ADB工具应用
次要用一下几条指令:
ADB connect IPADB rootADB remountADB push windows门路 /bin补充一条 ADB devices ADB didconnect,因为ADB常常断连贯所以须要查看和排除连贯问题
二、避坑指南
- ioctl版本
ioctl在改版之后也有2种,次要是为了解决零碎兼容的问题,分为compat_ioctl与unlocked_ioctl,安卓零碎也分为32位与64位版本,32位零碎,内核版本64位则须要应用compat_ioctl,如果零碎和内核版本都是32位或者64位则会调用unlock_ioctl,这一点如果不留神就会发现调用ioctl时候会报错,须要大家留神。
- 编译工具
在安卓跑驱动的时候须要用专门的工具来编译应用程序,还有穿插编译生成驱动文件的编译器,都有具体的版本要求,须要大家联合本人的设施抉择。
三、源码浏览
开始带大家读代码,大部分的问题都藏在代码中或者能够通过梳理代码解决,RTFSC!
先上驱动代码
#include <linux/init.h>#include <linux/module.h>#include <linux/cdev.h>#include <linux/fs.h> //函數指針寄存#include <linux/device.h>#include <linux/slab.h>#include <asm/uaccess.h>#include <linux/kernel.h>#include "in_proc.h"//指定的主设施号#define MAJOR_NUM 250/*struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count;}; *///注册本人的设施号struct mycdev { int len; unsigned int buffer[50]; struct cdev cdev;};MODULE_LICENSE("GPL"); //許可說明//设施号static dev_t dev_num = {0};//全局gcdstruct mycdev *gcd;//设施类struct class *cls;//取得用户传递的数据,依据它来决定注册的设施个数static int ndevices = 1;module_param(ndevices, int, 0644);MODULE_PARM_DESC(ndevices, "The number of devices for register.\n");//关上设施static int dev_fifo_open(struct inode *inode, struct file *file){ struct mycdev *cd; //用struct file的文件公有数据指针保留struct mycdev构造体指针 cd = container_of(inode->i_cdev,struct mycdev,cdev); //该函数能够依据构造体成员获取构造体地址 file->private_data = cd; printk( KERN_CRIT "open success!\n"); return 0;}//读设施static ssize_t dev_fifo_read(struct file *file, char __user *ubuf, size_t size, loff_t *ppos){ int n ; int ret; unsigned int *kbuf; struct mycdev *mycd = file->private_data; //printk(KERN_CRIT "read *ppos : %lld\n",*ppos); //光标地位 if(*ppos == mycd->len) return 0; //申请大小 > buffer残余的字节数 :读取理论记的字节数 if(size > mycd->len - *ppos) n = mycd->len - *ppos; else n = size; //从上一次文件地位指针的地位开始读取数据 kbuf = mycd->buffer+*ppos; //拷贝数据到用户空间 ret = copy_to_user(ubuf,kbuf,n*sizeof(kbuf)); if(ret != 0) return -EFAULT; //更新文件地位指针的值 *ppos += n; printk(KERN_CRIT "dev_fifo_read success!\n"); return n;}//写设施static ssize_t dev_fifo_write(struct file *file, const char __user *ubuf, size_t size, loff_t *ppos){ int n; int ret; unsigned int *kbuf; struct mycdev *mycd = file->private_data; printk("write *ppos : %lld\n",*ppos); //曾经达到buffer尾部了 if(*ppos == sizeof(mycd->buffer)) return -1; //申请大小 > buffer残余的字节数(有多少空间就写多少数据) if(size > sizeof(mycd->buffer) - *ppos) n = sizeof(mycd->buffer) - *ppos; else n = size; //从上一次文件地位指针的地位开始写入数据 kbuf = mycd->buffer + *ppos; //拷贝数据到内核空间 ret = copy_from_user(kbuf, ubuf, n*sizeof(ubuf)); if(ret != 0) return -EFAULT; //更新文件地位指针的值 *ppos += n; //更新dev_fifo.len mycd->len += n; printk("dev_fifo_write success!\n"); return n;}//linux 内核在2.6当前,曾经废除了ioctl函数指针构造,取而代之的是unlocked_ioctllong dev_fifo_unlocked_ioctl(struct file *file, unsigned int cmd,unsigned long arg){ int ret = 0; struct mycdev *mycd = file->private_data; struct ioctl_data val; //定义构造体 printk( KERN_CRIT "in dev_fifo_ioctl,sucessful!\n"); if(_IOC_TYPE(cmd) != DEV_FIFO_TYPE) //检测命令 { printk(KERN_CRIT "CMD ERROR\n"); return -EINVAL; } /*if(_IOC_NR(cmd) > DEV_FIFO_NR) { printk(KERN_CRIT "CMD 1NUMBER ERROR\n"); return -EINVAL; }*/ switch(cmd){ case DEV_FIFO_CLEAN: printk("CMD:CLEAN\n"); memset(mycd->buffer, 0, sizeof(mycd->buffer)); break; case DEV_FIFO_SETVALUE: printk("CMD:SETVALUE\n"); mycd->len = arg; break; case DEV_FIFO_GETVALUE: printk("CMD:GETVALUE\n"); ret = put_user(mycd->len, (int *)arg); break; case DEV_FIFO_SUM: //求和 printk( KERN_CRIT "It is CMD:SUM!!!!\n"); //查看指针是否平安|获取arg传递地址 if(copy_from_user(&val,(struct ioctl_data*)arg,sizeof(struct ioctl_data))){ ret = -EFAULT; goto RET; } //printk( KERN_CRIT "CMD:SUM!!!!!\n"); memset(mycd->buffer,0,DEV_SIZE); //清空区域 val.buf[1] = val.buf[0]+val.buf[2]; //计算结果 //printk(KERN_CRIT "result is %d\n",val.buf[1]); memcpy(mycd->buffer,val.buf,sizeof(val.buf)*val.size); /*for(i = 0;i < 50;i++) printk( KERN_CRIT "%d\n",mycd->buffer[i]);*/ mycd->len = val.size; file->f_pos = 0; break; case DEV_FIFO_DEC: //求差 printk(KERN_CRIT "It is CMD:DEC!!!!\n"); //查看指针是否平安|获取arg传递地址 if(copy_from_user(&val,(struct ioctl_data*)arg,sizeof(struct ioctl_data))){ ret = -EFAULT; goto RET; } memset(mycd->buffer,0,DEV_SIZE); //清空区域 val.buf[1] = val.buf[0]-val.buf[2]; //计算结果 memcpy(mycd->buffer,val.buf,sizeof(val.buf)*val.size); /*printk(KERN_CRIT"size is :%d\n",val.size); for(i = 0;i < 50;i++) printk( KERN_CRIT "%d\n",mycd->buffer[i]);*/ mycd->len = val.size; file->f_pos = 0; break; default: return -EFAULT; //谬误指令 } RET: return ret;}//设施操作函数接口static const struct file_operations fifo_operations = { .owner = THIS_MODULE, .open = dev_fifo_open, .read = dev_fifo_read, .write = dev_fifo_write, .compat_ioctl = dev_fifo_unlocked_ioctl,};//模块入口int __init dev_fifo_init(void){ int i = 0; int n = 0; int ret; struct device *device; gcd = kzalloc(ndevices * sizeof(struct mycdev), GFP_KERNEL); //内核内存失常调配,可能通过睡眠来期待,此函数等同于kmalloc() if(!gcd){ return -ENOMEM; //內存溢出 } //设施号 : 主设施号(12bit) | 次设施号(20bit) dev_num = MKDEV(MAJOR_NUM, 0); //动态注册设施号 ret = register_chrdev_region(dev_num,ndevices,"dev_fifo"); if(ret < 0){ //动态注册失败,进行动静注册设施号 ret = alloc_chrdev_region(&dev_num,0,ndevices,"dev_fifo"); if(ret < 0){ printk("Fail to register_chrdev_region\n"); goto err_register_chrdev_region; } } //创立设施类 cls = class_create(THIS_MODULE, "dev_fifo"); if(IS_ERR(cls)){ ret = PTR_ERR(cls); goto err_class_create; } printk("ndevices : %d\n",ndevices); for(n = 0;n < ndevices;n ++){ //初始化字符设施 cdev_init(&gcd[n].cdev,&fifo_operations); //增加设施到操作系统 ret = cdev_add(&gcd[n].cdev,dev_num + n,1); if (ret < 0){ goto err_cdev_add; } //导出设施信息到用户空间(/sys/class/类名/设施名) device = device_create(cls,NULL,dev_num + n,NULL,"dev_fifo%d",n); if(IS_ERR(device)){ ret = PTR_ERR(device); printk("Fail to device_create\n"); goto err_device_create; } } printk("Register dev_fito to system,ok!\n"); return 0;err_device_create: //将曾经导出的设施信息除去 for(i = 0;i < n;i ++){ device_destroy(cls,dev_num + i); }err_cdev_add: //将曾经增加的全副除去 for(i = 0;i < n;i ++) { cdev_del(&gcd[i].cdev); }err_class_create: unregister_chrdev_region(dev_num, ndevices);err_register_chrdev_region: return ret;}void __exit dev_fifo_exit(void){ int i; //删除sysfs文件系统中的设施 for(i = 0;i < ndevices;i ++){ device_destroy(cls,dev_num + i); } //删除零碎中的设施类 class_destroy(cls); //从零碎中删除增加的字符设施 for(i = 0;i < ndevices;i ++){ cdev_del(&gcd[i].cdev); } //开释申请的设施号 unregister_chrdev_region(dev_num, ndevices);}module_init(dev_fifo_init);module_exit(dev_fifo_exit);
接下来是利用代码:
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <string.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/ioctl.h>#include "in_proc.h"static void Hello_Panel(void) //操作提醒界面{ printf("Welcome to the panel!\n"); printf("eg:A +(-) B\n"); }static int Char_prosess(char po) //符号读取{ if(po == '+') return 0; else{ if(po == '-') return 1; else return -1; }}int main(int argc, const char* argv[]){ int ret = 0,fd = 0,n = 0,Char_error = 2; unsigned int Result_buf[20]; struct ioctl_data my_data; my_data.size = argc-1; /*printf("%d\n",argc-1 ); //测试应用 for(n = 0;n < argc-1;n++) { my_data.buf[n] = (int)(argv[n+1]); printf("%s\n",my_data.buf[n]); }*/ Char_error = Char_prosess(*argv[2]); printf("%d\n", Char_error); for(n = 0;n < argc-1;n++){ //参数读取 my_data.buf[n] = atoi((argv[n+1])); //printf("%d\n",my_data.buf[n]); } switch(Char_error){ //测试应用 case 0: printf("right:+ \n"); break; case 1: printf("right:- \n"); break; case -1: printf("error!!!\n"); Hello_Panel(); return -1; break; } fd = open("/dev/dev_fifo0",O_RDWR); if(fd < 0){ perror("Fail to open"); return -1; } //printf("open sucessful!!,fd is :%d\n", fd); if(Char_error == 0) //加法 ioctl(fd,DEV_FIFO_SUM,&my_data); else ioctl(fd,DEV_FIFO_DEC,&my_data); ret = read(fd,Result_buf,3); if(Char_error == 0) printf("%d + %d = %d\n",Result_buf[0],Result_buf[2],Result_buf[1]); else if(Char_error == 1) printf("%d - %d = %d\n",Result_buf[0],Result_buf[2],Result_buf[1]); /*for(n = 0;n < 3;n++){ printf("result is:%d\n",Result_buf[n]); }*/ close(fd); return 0;}
代码重要的中央曾经正文,无非就是按驱动的流程,开始造轮子,组装,启动以及进行四个步骤,还是须要读者本人建立代码,多读,多去了解前辈的思维。整个代码格调有些怪异,因为楼主在承受新格调毒打(哈哈哈),还没齐全把新格调融到本人原来的格调中,所以有些中央解决的不三不四,并且程序没有通过优化,可能会让一些前辈恼火,欢送前辈们给与楼主以指教!
最初,留个坑,大家缓缓填吧!
四.写在最初的话
参考博客,非常感谢前人的致力
http://blog.chinaunix.net/uid-26833883-id-4371047.html
https://www.cnblogs.com/yangguang-it/p/8681579.html
https://www.cnblogs.com/eleclsc/p/11533682.html
linus曾说:My name is Linus, and I am your God”,置信大家能够感触“神”的气场吧,所以致力吧,说不定哪天也能够在内核的某个角落看到author一栏有各位的名字,hahaha