Linux驱动开发学习记录一

前言

大多数应用开发人员使用高级语言进行开发,这些高级语言都提供了标准库或者API最终还是通过系统调用来实现操作IO设备的,系统调用是操作系统提供的,它是操作系统的一部分。
系统调用封装了对硬件操作的所有细节,而标准库或者SDK在系统调用的基础上做了高度抽象的封装和优化

系统调用

应用程序是没有权限求直接访问系统资源,当应用程序有访问的需要时,操作系统提供系统调用,让应用程序统一通过系统调用来访问系统资源,这里所说的系统资源包括文件,网络,内核,各类IO口设备等

模式切换

一个完整的应用程序分为两个部分,一部分是应用程序的代码和数据,另一部分是内核的代码和数据,切换模式就是这两部分的分水岭,意味着处理器进入了一个不同的模式,不同的模式就是不同的世界,不同的世界就有不同的权限,而内核态模式就是王者,可以掌握所有的资源,用户态模式只能掌握自己的一亩三分地。

Linux驱动开发思维

1、 Linux下 驱动开发直接操作寄存器不现实。
2、根据Linux下的各种驱动框架进行开发,一定要满足框架,也就是Linux下各种驱动框架的掌握
3、驱动最终表现就是 /dev/xxx 文件 。打开,关闭,读写。。
4、现在新的内核支持设备树,这个一个.dts文件,此文件,描述了板子的设备信息

驱动分类

1、字符设备(最多的),
2、块设备
3、网络设备驱动

如何理解Bootloader

1、如果它可以将操作系统内核复制到内存中运行,无论从本地(如Flash)还是通过网络,我们就可以称这段程序为Bootloader
2、Bootloader就是这么一段小程序,它在系统上电时开始执行
初始化硬件设备,准备好软件环境,最后调用操作系统内核

值得一说的是,CPU上电后,会从某个地址开始执行,在嵌入式开发板中我们将ROM 或 Flash等映射到这个地址,Bootlaoder就存放在地址开始处,这样一上电就可以开始执行

Booeloader启动的两个阶段

1、硬件设备初始化(WATCHDOG, 关中断 , 设置CPU的速度和时钟频率, RAM初始化)
2、为加载Bootloader的第二阶段准备RAM空间
3、复制Bootloader的第二阶段代码到RAM空间中
3、设置好栈
4、跳转到第二阶段代码的c入口点

这里补充一波 RAM, ROM 和 FLASH的区别

ROM:
ROM中所存数据稳定,一旦存储数据就再也将之改变或者删除,断电后所存数据也不会消失。ROM中所存在数据,一旦存储数据就再也无法将改变或者删除吗,断电后所存数据也不会消失。单片机中用来存储程序数据及常量数据或变量数据,凡是c文件及h文件中所有代码、全局变量、局部变量、‘const’限定符定义的常量数据、startup.asm文件中的代码(类似ARM中的bootloader或者X86中的BIOS,一些低端的单片机是没有这个的)通通都存储在ROM中。
FLASH存储器:
Flash存储器又称闪存。它是EEPROM的一种,它结合了ROM和RAM的长处。不仅具备电子可擦除可编辑(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据。它于EEPROM的最大区别是,FLASH按扇区(block)操作,而EEPROM按照字节操作。FLASH的电路结构较简单,同样容量占芯片面积较小,成本自然比EEPROM低,因此适合用于做程序存储器。
RAM随机访问存储器:
RAM又称随机存取存储器,存储单元的内容可按照需要随机取出或存入,且存取的速度与存储单元的位置无关。这种存储器在断电时,将丢失其存储内容,所以主要用于存储短时间使用的程序。
它主要用来存储程序中用到的变量。凡是整个程序中,所用到的需要被改写的量(包括全局变量,局部变量,堆栈段等),都存在RAM中

Bootloader第二阶段的功能

1、初始化本阶段要使用到的硬件设备
2、检测系统内存映射(memory map)
3、将内核映射和根文件系统映射从Flash上读到RAM空间中
4、为内核设置启动参数
5、调用内核
/为了方便开发至少初始化一个串口,用来供给程序员与Bootloader进行交互/

U_Boot顶层Makefile分析

==1、make是支持递归调用的,主内目录可以使用 $(MAKE) -C 目录名 来编译子目录的Makefile ==
$(MAKE)就是调用“make”命令 , -C指定子目录。
主目录传递变量通过
export 变量名 //导出变量名给make
unexport 变量名 // 不导出变量名给子make

shell中 的“ | ”

shell中的“ | ” 表示管道, 意思是将左边的输出当做右边的输入

Linux内核支持的模块参数类型

bool(布尔值( true/False ), 关联的变量应该是int类型)
invbool ( bool的反值,例如赋值为(False)实际为 Ture)
重点:charp(字符型指针,内核为用户提供的字符串分配内存,并设置此指针保存其首地址)
int ( 整形 )
long ( 常整形 )
short ( 短整形 )
uint ( 无符号整形 )
ulong ( 无符号长整形 )
ushort ( 无符号短整形 )

驱动模块加载查看命令

make : 编译
lsmod : 显示内核中已经安装成功的模块(可以通过 lsmod | grep 模块名 快速查看一个模块是否安装成功

/grep 指令用于查找内容包含指定的范本样式的文件,如果发现某文件的内容符合所指定的范本样式,预设 grep 指令会把含有范本样式的那一列显示出来/

insmod/rmmod : 安装/卸载模块
** dmesg ** : 打印log信息(可以看到 printk 打印的信息)(dmesg -c 为清除log信息

makefile 中的 shell

在makefile中要使用shell命令必须加shell 例如$( shell pwd ), 不加的话会是个空值

这里加深记忆一个知识点 : Makefile中加打印信息的 语法格式为 $(info " 要打印的语句 ")

*** shell 中 变量的定义 ‘=’ 前后不应该有空格

linux中 文件重命名命令

mv 原文件名 修改的文件名

设备号

查看设备号命令

cat /proc/devices
/加深印象的笔记: ls -l 用来查看文件的属性(权限)/

设备号相关函数

alloc_chrdev_region // 自动分配设备号
register_chrdev_region // 分配以设定的设备号
MAJOR // 宏, 提取主设备号
MINOR // 宏 , 提取次设备号
== MKDEV // 将指定的主设备号和次设备号 ,转换为一个dev_t==

static struct char_device_struct {
    struct char_device_struct *next;
    unsigned int major;
    unsigned int baseminor;
    int minorct;
    char name[64];
    struct cdev *cdev;        /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];   --CHRDEV_MAJOR_HASH_SIZE = 255

一个 cdev 一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:

struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;

动态内存定义初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;

两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。

下面贴出了两个函数的代码,以具体看一下它们之间的差异。
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if § {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}

当一个字符设备不再需要的时候(比如模块卸载),就可以用cdev_del()函数来释放cdev占用的内存

```cpp
void cdev_del(struct cdev *p)
{
    cdev_unmap(p->dev, p->count) ;
    kobject_put(&p->kobj);
}

man 帮助命令的使用

man 2 函数名 //可以查看函数的使用,包括头文件的引用,这里的函数指的是内核函数

mknod命令的使用(创建文件节点 )

1、创建特殊文件

mknod Name { b | c} Major Minor

b: 表示特殊文件名是面向块的设备(磁盘,软盘或磁带)
c: 表示特殊文件是面向字符的设备 (其他设备)

Name:给这个设备附一个名称一般为(/dev/名称)

  • 比较重要的就是这主设备号(Major)和 次设备号(Minor),需要首先创建这么一个设备节点,后面的驱动程序才能找到这么一个节点

2、 查看创建的文件节点
ls /dev/节点名称 -l

字符设备,驱动编写流程

// 注册设备号,当加载模块时,会进行设备号的注册工作
static int init_kun(void)
{
    int ret ;
    ret =  register_chrdev(KUN_MAGOR, KUN_NAME, &s3c24xxx_leds_fops) ;
    if(ret<0){
          printk("设备无法正常打开");
          return ret ;    
    }
printk("设备号注册成功")
return 0 ;
}

//注销设备号: 当卸载相应模块时,会进行设备号的注销
static void exit_kun(void)
{
     //卸载驱动程序
     unregister_chrdev(KUN_MAJOR, KUN_NAME);
}

moduble_init(init_kun);
moduble_init(exit_kun);
 
 /*来了,他来了 ,字符设备的核心
   当应用程序操作设备文件时所调用的open, read , write 等函数
   最终会调用这个结构中的对应函数
*/

static struct file_operations kun_fops = {
        .owner = THIS_MODULE ,/这是一个宏,指向编译模块时自动创建的__this_module  ,总得来说就是不用管*/
        .open = kun_open ,
        .ioctl = kun_ioctl,
};

file_opera 类型的结构是驱动中最重要的数据结构,编写字符设备驱动程序的主要工作也是填充其中的各个成员。

系统调用read , write

进程与文件描述符

lsof + 文件名 (可以知道这个文件被哪些进程打开了)

/proc/pid(进程号,用lsof查看)/fd (可以看出进程打开的文件)

/知识点加强(ls -l (可以打印文件的详细信息))/

vim使用小窍门收集

使用场合:当编译文件(test.c)出现一个错误,在终端的打印信息中,会显出,错误出现在第几行。
此时我们可以:
vim test.c +行数 (这样可以直接定义到出错误的行数)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值