一,简述
(1)Linux系统组成
()app: [0-3G]
---------------------------------系统调用(软中断)---------------------
kernel: 【3-4G】
5种功能:
进程管理:进程的创建、销毁、调度等功能
文件管理:通过文件系统ext2/ext3/ext4 yaff jiffs等来组织管理文件
网络管理 :通过网络协议栈对数据进程封装和拆解的过程
内存管理 :通过内存管理器对用户空间和内核空间内存的申请和释放
设备管理:设备驱动的管理
字符设备驱动:
- 按照字节为单位进行访问,顺序访问
- 会创建设备文件,open read write close来访问
块设备驱动:
- 按照块(512字节)(扇区)来访问,可以顺序访问,可以无序访问
- 会创建设备文件,open read write close来访问
网卡设备驱动:按照网络数据包来收发的
(2)驱动移植
- 需要有一个驱动对应的 .c代码
- 把.c文件放入到对应的文件夹内(char)
- 修改Makefile-》添加上自己代码编译生成的.o文件-》保存退出
- 修改Kconfig生成自己的菜单 (3和4根据其他的仿写)-》保存退出
- 到顶层目录执行make menuconfig-》配置自己的驱动(M)
- 编译-》make modules
- 找到生成的.ko文件安装(insmod lcd.ko )
编译:
make uImage-->uImage(包含了新的驱动的内核)
make modules -->demo.ko
Makefile modules:
编译模块的规则
Y(要编译到内核中) M(编译生成模块) N(不编译驱动)
sudo insmod demo.ko 安装驱动
sudo rmmod demo 卸载驱动
静态编译:编译之后生成的可执行程序可以单独执行
动态编译:编译之后生成的可执行程序需要依赖其他内核才能执行
内部编译:在内核源码树中编译
外部编译:在内核源码树外编译
(3)驱动模块
入口(安装):资源的申请
出口(卸载):资源的释放
许可证:GPL
#include <linux/init.h>
#include <linux/module.h>
static int __init hello_init(void)
//__init将hello_in it放到.init.text段中
{return 0;
}
static void __exit hello_exit(void)//__exit将hello_exit放到.exit.text段中
{
}
module_init(hello_init);
//告诉内核驱动的入口地址
module_exit(hello_exit);
//告诉内核驱动的出口地址
MODULE_LICENSE("GPL");
//许可证
(4)字符设备驱动
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
功能:注册一个字符设备驱动
参数:
@major:主设备号
:如果你填写的值大于0,它认为这个就是主设备号
:如果你填写的值为0,操作系统给你分配一个主设备号
@name :名字 cat /proc/devices 查看设备名和主设备号
@fops :操作方法结构体
返回值:major>0 ,成功返回0,失败返回错误码(负数) (vi -t EIO 可以查看错误码)
major=0,成功主设备号,失败返回错误码(负数)
void unregister_chrdev(unsigned int major, const char *name)
功能:注销一个字符设备驱动
参数:
major:主设备号
name:名字
返回值:无
二,相关知识点
应用程序如何将数据传递给驱动(读写方向站在用户角度)
int copy_from_user(void *to, const void __user *from, int n)
功能:从用户空间拷贝数据到内核空间
参数:
to:内核中内存的首地址 from:用户空间的首地址 n:拷贝数据的长度
返回值:成功返回0,失败返回未拷贝的字节个数
int copy_to_user(void __user *to,const void *from,int n)
功能:从内核空间拷贝数据到用户空间
参数:
to:用户空间内存的首地址 from:内核空间的首地址 n:拷贝数据的长度
返回值:成功返回0,失败返回未拷贝的字节个数
控制LED灯:
驱动如何操作寄存器
rgb_led灯的寄存器是物理地址,在linux内核启动之后,
在使用地址的时候,操作的全是虚拟地址。需要将物理地址
转化为虚拟地址。在驱动代码中操作的虚拟地址就相当于
操作实际的物理地址。
物理地址<------>虚拟地址
void * ioremap(phys_addr_t offset, unsigned long size)
功能:将物理地址映射成虚拟地址
参数:
@offset :要映射的物理地址
@size :大小(字节)
返回值:成功返回虚拟地址,失败返回NULL;
void iounmap(void *addr)
功能:取消映射
参数:
@addr :虚拟地址
返回值:无
RGB_led
red :gpioa28
GPIOXOUT :控制高低电平的 0xC001A000
GPIOxOUTENB:输入输出模式 0xC001A004
GPIOxALTFN1:function寄存器 0xC001A024
green:gpioe13
0xC001e000
blue :gpiob12
0xC001b000
指针类型加1是加的他的类型大小
驱动控制灯
三,代码开发
驱动层
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/device.h>#define RED_BASE 0xC001A000 //红灯基地址
#define BLUE_BASE 0xC001B000 //蓝灯基地址
#define GREEN_BASE 0xC001E000 //绿灯基地址
#define FMQ_BASE 0xC001C000 //蜂鸣器基地址unsigned int *red_base = NULL;//申请的红灯虚拟地址
unsigned int *blue_base = NULL;//申请的蓝灯虚拟地址
unsigned int *green_base = NULL;//申请的绿灯虚拟地址
unsigned int *fmq_base = NULL;//申请的蜂鸣器虚拟地址#define CNAME "hello"//设备名称
int major = 0;//设备号
int r_led=0,b_led=0,g_led=0;
char kbuf[128] = {0};//数据缓存区
struct class *cls;//自动创建设备节点 目录返回
struct device *dev;//自动创建设备节点 信息返回
int mycdev_open(struct inode *inode, struct file *file)//自己的open函数
{printk("open\n");//应用层使用open函数时会打印这句信息return 0;
}
ssize_t mycdev_read(struct file *file, char __user *ubuf,//自己写的read函数size_t size, loff_t *offs)
{memset(kbuf,0,sizeof(kbuf));int ret;printk("read is 111\n");//应用层使用read函数时会打印这句信息
//红灯if(r_led){strcat(kbuf,"red_led_open;");}else{strcat(kbuf,"red_led_close;");}//蓝灯if(b_led){strcat(kbuf,"blue_led_open;");}else{strcat(kbuf,"blue_led_close;");}
//绿灯if(g_led){strcat(kbuf,"green_led_open;");}else{strcat(kbuf,"green_led_close;");}if(size > sizeof(kbuf))size = sizeof(kbuf);//判断应用层传来的数据是不是大于咱们自己声明的数组长度,如果大于给转成128ret=copy_to_user(ubuf,kbuf,size);//将驱动层kbuf内数据读到应用层if(ret){ //如果错误进入printk("copy to user error\n");return -EINVAL;//返回错误提示}return size;
}
ssize_t mycdev_write(struct file *file, const char __user *ubuf,size_t size, loff_t *offs)
{int ret;printk("this is write\n");//应用层使用write函数时会打印这句信息if(size > sizeof(kbuf)) size = sizeof(kbuf);//判断应用层传来的数据是不是大于咱们自己声明的数组长度,如果大于给转成128ret=copy_from_user(kbuf,ubuf,size);//将应用层ubuf内数据写到驱动层if(ret){printk("copyfrom user error\n");return -EINVAL;}
//红灯if(strncmp(kbuf,"11",2)==0){//灭灯*red_base &= ~(1<<28);r_led=0;}if(strncmp(kbuf,"22",2)==0)//亮灯{*red_base |= (1<<28);r_led=1;}
//蓝灯if(strncmp(kbuf,"33",2)==0){//灭灯*blue_base &= ~(1<<12);b_led=0;}if(strncmp(kbuf,"44",2)==0){//亮灯*blue_base |= (1<<12);b_led=1;}
//绿灯if(strncmp(kbuf,"55",2)==0){//灭灯*green_base &= ~(1<<13);g_led=0;}if(strncmp(kbuf,"66",2)==0){//亮灯*green_base |= (1<<13);g_led=1;}
//蜂鸣器if(strncmp(kbuf,"77",2)==0){//蜂鸣器关*fmq_base &= ~(1<<14);}if(strncmp(kbuf,"88",2)==0){//蜂鸣器开*fmq_base |= (1<<14);}return size;
}
int mycdev_close(struct inode *inode, struct file *file)
{printk("close");//应用层使用close函数时会打印这句信息return 0;
}const struct file_operations fops = {//这个结构体为咱们自己写的,在注册驱动时被调用,这是APP层能调用驱动层上面咱们自己的的程序的关键.open = mycdev_open,//将自己写的open函数给到API内.read = mycdev_read,//将自己写的read函数给到API内.write = mycdev_write,//将自己写的write函数给到API内.release = mycdev_close,//将自己写的close函数给到API内
};static int __init mycdev_init(void)
{//注册字符设备驱动major = register_chrdev(major,CNAME,&fops);if(major < 0){printk("register device error\n");//注册失败打印return major;}red_base = ioremap(RED_BASE,36);//物理地址转换虚拟地址 红灯GPIOa28if(red_base == NULL){//转换失败提示printk("red ioremap error\n");return -ENOMEM;}blue_base = ioremap(BLUE_BASE,36);//物理地址转换虚拟地址 if(blue_base == NULL){//转换失败提示printk("blue ioremap error\n");return -ENOMEM;}green_base = ioremap(GREEN_BASE,36);//物理地址转换虚拟地址if(green_base == NULL){//转换失败提示printk("green ioremap error\n");return -ENOMEM;}fmq_base = ioremap(FMQ_BASE,36);//物理地址转换虚拟地址if(fmq_base == NULL){//转换失败提示printk("fmq ioremap error\n");return -ENOMEM;}
//红灯部分配置*red_base &= ~(1<<28);//配置为灭灯*(red_base+1) |= (1<<28);//配置为输出模式*(red_base+9) &= ~(3<<24);//配置为GPIO功能,基地址+9,16进制36是24
//蓝灯部分配置*blue_base &= ~(1<<12);*(blue_base+1) |= (1<<12);*(blue_base+8) &= ~(3<<24);*(blue_base+8) |= (1<<25);
//绿灯部分配置*green_base &= ~(1<<13);*(green_base+1) |= (1<<13);*(green_base+8) &= ~(3<<26);
//蜂鸣器部分配置*fmq_base &= ~(1<<14);*(fmq_base+1) |= (1<<14);*(fmq_base+8) &= ~(3<<28);*(fmq_base+8) |= (1<<28);//自动创建设备节点cls = class_create(THIS_MODULE,CNAME);//自动创建设备节点目录if(IS_ERR(cls)){//IS_ERR可以将cls 错误码指针转换成错误码printk("class create error\n");return PTR_ERR(cls);//失败返回错误码}dev = device_create(cls,NULL,MKDEV(major,0),NULL,CNAME);//自动创建设备节点信息if(IS_ERR(dev)){printk("device create error\n");return PTR_ERR(dev);//失败返回错误码}return 0;
}static void __exit mycdev_exit(void)//出口
{device_destroy(cls,MKDEV(major,0));//注释掉自动创建节点 信息class_destroy(cls);//注释掉自动创建节点 目录iounmap(green_base);//注销掉虚拟地址,注意后申请的先注销iounmap(blue_base);iounmap(red_base);//注销字符设备驱动unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
应用层(控制LED灯,蜂鸣器)
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>char buf[6] = {0};//数据缓存int main(int argc, const char *argv[])
{int fd;fd = open("./hello",O_RDWR);//打开文件if(fd == -1){perror("open error");//打开错误提示return -1;}while(1){write(fd,buf,sizeof(buf));//将buf写入驱动中用于控制LED亮灭sleep(1);//延时//buf[0] = buf[0]?0:1;//如果buf[0]为0则赋值1,不为赋值0
//红灯if(strncmp(buf,"11",2)==0){//灭灯printf("红灯灭");}else if(strncmp(buf,"22",2)==0)//亮灯{printf("红灯亮");}
//蓝灯else if(strncmp(buf,"33",2)==0){//灭灯printf("蓝灯灭")}else if(strncmp(buf,"44",2)==0){//亮灯printf("蓝灯亮");}
//绿灯else if(strncmp(buf,"55",2)==0){//灭灯printf("绿灯灭");}else if(strncmp(buf,"66",2)==0){//亮灯printf("绿灯亮");}
//蜂鸣器else if(strncmp(buf,"77",2)==0){printf("蜂鸣器关");}else if(strncmp(buf,"88",2)==0){printf("蜂鸣器开");}}close(fd);//关闭return 0;
}