Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统

在产品将要上线之前,需要制作不同类型格式的根文件系统

在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统

优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命

【1】重启上位机nfs服务

sudo service nfs-kernel-server restart

【2】关闭上位机防火墙

sudo service ufw stop

【3】修改下位机的环境变量

setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/nfs_share/_install ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 init=/linuxrc console=ttySAC0,115200 maxcpus=1 lcd=wy070ml tp=gslx680-linux

【4】保存环境变量

saveenv

【5】重启

re

二、Linux内核驱动开发的基础知识

1、裸板驱动和内核驱动的区别

1)裸板驱动

主观性较强,相当于英语考试中的作文模块

while (1) {
    // 代码逻辑
    ...
}
2)Linux下的驱动开发

客观性较强,相当于英语考试中的完形填空

需要的知识:

【1】硬件的知识

读懂硬件原理图、读懂读写时序图、各种接口协议

【2】读取cpu数据手册

各种寄存器如何配置、各种外设如何配置

【3】驱动的编码框架

字符设备驱动(按字节访问,顺序固定)

块设备驱动(按块访问,顺序不固定)

网络设备驱动(按字节访问,顺序固定)

【4】内核态的编程规则

用户态和内核态的数据交互

内核模块的编程框架

解决竞态和并发

。。。

2、Linux内核代码的特点

1)介绍

Linux内核本质上就是一个巨大的裸板程序,所有的函数都是自身实现的

|----------|----------|------------------|------|
| 标准C库 | 系统调用 | Linux Kernel | 说明 |
| fopen | open | sys_open | 打开文件 |
| fclose | close | sys_close | 关闭文件 |
| fread | read | sys_read | 读文件 |
| fwrite | write | sys_write | 写文件 |

学习Linux内核最好的老师就是内核源码,遇到不会用的函数,去找内核源码。

2)推荐书籍

内核:<Linux内核的设计与实现>

驱动:<LDD3>、<Linux设备驱动第三版>

3、Linux内核需要注意的地方

1)Linux内核不允许做浮点运算

2)Linux内核中使用的是GUN C,不是标准C(GNU C是标准C的扩展版)

3)Linux内核中每个线程都有两个栈

【1】用户态的栈

【2】内核态的栈

4)Linux内核使用的内存空间是3G - 4G

5)Linux内核更加注重代码的执行效率和可移植性

4、搭建Linu内核的开发环境

1)安装交叉编译器

2)获取一份x6818上使用的Linux内核源码

3)编译Linux内核源码

4)制作并移植根文件系统

5、使用source insight建立一个内核源码的项目工程

source insight是一个阅读项目工程源码非常好用的工具

官网:Source Insight Programming Editor and Code Browser

1)安装source insight

序列号位置:sourcesightSN.txt

2)添加Linux Kernel源码

【1】在window下某盘符中创建一个目录

【2】将Linux内核源码拷贝并解压到该目录

【3】打开kernel文件夹,新建文件夹,用来存储项目工程文件

3)在source insight中添加项目工程

6、编写内核的模块文件(*.ko)

1)上位机编写模块文件

【1】在虚拟机创建新目录,存放工程目录

mkdir drivers

【2】进入drivers目录

cd drivers/

【3】创建第一个工程目录

mkdir hello_pro

【4】进入hello_pro目录

cd hello_pro/

【5】编写工程文件hello.c

vim hello.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");    // 声明开源,
                          // 如果不添加该声明,内核会报受到污染,因为没有遵守开源规则
MODULE_AUTHOR("Zjd");     // 声明作者

int __init hello_init(void)
{
    printk("<0>" "Hello, my owner kernel!\n");

    return 0;
}

void __exit hello_exit(void)
{
    printk("<0>" "bye, my owner kernel!\n");

    return ;
}

module_init (hello_init);
module_exit (hello_exit);

【6】编写Makefile

vim Makefile

cpp 复制代码
obj-m += hello.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel

all:
    make -C $(KERNEL_PATH) M=$(PWD) modules

clean:
    make -C $(KERNEL_PATH) M=$(PWD) clean

【7】编译工程文件

make

【8】将工程文件拷贝到共享的根文件系统中

cp hello.ko /nfs_share/_install/

2)下位机验证模块文件

【1】查看内核中的模块

lsmod

【2】安装模块

insmod hello.ko

【3】卸载模块

rmmod hello

提示缺少 /lib/modules/ 目录

mkdir /lib/modules

提示缺少 3.4.39-embTwoGroup 目录

mkdir /lib/modules/3.4.39-embTwoGroup

卸载成功

7、导出符号

在C语言中,使用extern关键字修饰的符号可以跨模块访问

进行内核态开发时,需要额外添加一个宏去修饰

EXPORT_SYMBOL:它所修饰的符号,在内核中,所有的模块都可以访问到

EXPORT_SYMBOL_GPL:它所修饰的符号,在内核中,只有遵循GPL规则的模块才能访问到

1)上位机编写试验文件

【1】创建新的项目工程

mkdir export

【2】进入实验目录

cd extern_pro

【3】创建文件

touch export.c import.c export.h

【4】编写程序

vim export.h

cpp 复制代码
#ifndef __EXPORT_H
#define __EXPORT_H

extern int add(int a, int b); 

#endif

vim export.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int add(int a, int b)
{
    return a + b;
}

EXPORT_SYMBOL(add);

vim import.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include "export.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int __init import_init(void)
{
    int ret = add(20, 6); 

    printk("<0>" "ret = %d\n", ret);

    return 0;
}

void __exit import_exit(void)
{
    printk("<0>" "I'm going.\n");

    return ;
}

module_init(import_init);
module_exit(import_exit);

【5】编写Makefile

vim Makefile

bash 复制代码
obj-m += export.o import.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
    make -C $(KERNEL_PATH) M=$(PWD) modules
    cp *.ko $(ROOTFS_PATH)

clean:
    make -C $(KERNEL_PATH) M=$(PWD) clean
2)下位机验证

【1】挂载export.ko

insmod export.ko

【2】挂载import.ko

insmod import.ko

【3】卸载import

rmmod import

【4】挂载export

rmmod export

【5】注意事项

1】安装和卸载模块的顺序是相反的

2】安装时要填写 *.ko 文件,卸载时直接填写文件名 *

8、printk

1)简介

printk是内核中的打印函数,它输出到内核自己维护的缓冲区。

printk的使用方法:

printk("<0/1/2/3.../7>" "info");

注释:

<0/1/2/3.../7>:代表该条消息的打印优先级,越小优先级越高

info:代表要打印的消息

它的用法与printf相同,只不过前面多了一个消息优先级的配置,且不需要用 ',' 分开

2)特殊情况

printk("ret = %d\n", ret); // 使用的是默认优先级

Linux内核将打印优先级设定为8个等级(0~7),值越小,优先级越高

printk输出的信息先到内核维护的缓冲区

缓冲区的内容能不能输出到控制终端是有限制的

3)限制

在uboot中设置的bootargs环境变量中的loglevel代表Linux内核设置的优先级阈值

当我们设定printk的优先级大于loglevel所设置的优先级,则可以打到终端,相反则不可以打印到终端。

4)验证

【1】创建实验目录

mkdir printk_pro

【2】进入实验目录

cd printk_pro/

【3】编写程序

vim myprintk.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int __init printk_init(void)
{
	printk("<0>" "Level 0!\n");
	printk("<1>" "Level 1!\n");
	printk("<2>" "Level 2!\n");
	printk("<3>" "Level 3!\n");
	printk("<4>" "Level 4!\n");
	printk("<5>" "Level 5!\n");
	printk("<6>" "Level 6!\n");
	printk("<7>" "Level 7!\n");

	return 0;
}

void __exit printk_exit(void)
{
	return ;
}

module_init(printk_init);
module_exit(printk_exit);

【4】查看内核源码

在Source Insight中查找printk

【5】优先级说明

|--------------|---------|--------|
| 宏名 | 宏值 | 说明 |
| KERN_EMERG | "<0>" | 系统不可用 |
| KERN_ALERT | "<1>" | 立即操作 |
| KERN_CRIT | "<2>" | 临界条件 |
| KERN_ERR | "<3>" | 错误 |
| KERN_WARNING | "<4>" | 警告 |
| KERN_NOTICE | "<5>" | 正常但重要 |
| KERN_INFO | "<6>" | 消息 |
| KERN_DEBUG | "<7>" | 调试 |

【6】更改程序内容

vim myprintk.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int __init printk_init(void)
{
	printk(KERN_EMERG "Level 0!\n");
	printk(KERN_ALERT "Level 1!\n");
	printk(KERN_CRIT "Level 2!\n");
	printk(KERN_ERR "Level 3!\n");
	printk(KERN_WARNING "Level 4!\n");
	printk(KERN_NOTICE "Level 5!\n");
	printk(KERN_INFO "Level 6!\n");
	printk(KERN_DEBUG "Level 7!\n");

	return 0;
}

void __exit printk_exit(void)
{
	return ;
}

module_init(printk_init);
module_exit(printk_exit);

【7】编写Makefile

vim Makefile

bash 复制代码
obj-m += myprintk.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
    make -C $(KERNEL_PATH) M=$(PWD) modules
    cp *.ko $(ROOTFS_PATH)

clean:
    make -C $(KERNEL_PATH) M=$(PWD) clean

【8】下位机验证


查看内核优先级设置

cat /proc/sys/kernel/printk

第一个值:优先级阈值(这里是2)

第二个值:内核默认优先级(这里是4)


9、模块参数(三步法)

在C语言中,我们使用argc、argv给程序传参

./a.out xxx yyy zzz

// int main(int argc, char **argv)

int main(int argc, char *argv[]){

...

};

1)定义全局变量
cpp 复制代码
int irq = 0;
char *str = "Hello World!";
int fish[10] = {0};
int num = 10;

int __init module_param_init(void)
{
    ...;
    return 0;
}
2)通过特定的宏

module_param(name, type, perm)

name:要声明为模块参数的变量名

type:变量的类型

perm:权限

module_param_array(name, type, nump, perm)

name:要声明为模块参数的数组名

type:数组元素的类型

nump:数组元素个数指针

perm:权限

3)使用模块参数

insmod module_param.ko

insmod module_param.ko irq=10 str="easthome" fish=1,2,3,4,5,6

4)验证

【1】创建工程目录

mkdir module_param

【2】进入工程目录

cd module_param

【3】编写程序

vim module_param.c

cpp 复制代码
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zjd");

int irq = 0;
char *str = "hello world!";
int len = 10;
int arr[10] = {0};

module_param(irq, int, 0644);
module_param(str, charp, 0);
module_param_array(arr, int, &len, 0644);

int __init module_param_init(void)
{
	int i = 0;

	printk(KERN_EMERG "irq = %d\n", irq);
	printk(KERN_EMERG "str = %s\n", str);

	// Linux Kernel apply the define of ARRAY_SIZE 
	// to count the number of members
	// #define ARRAY_SIZE(x) sizeof(x) / sizeof(x[0])
	for (i = 0; i < ARRAY_SIZE(arr); i++) {
		printk(KERN_EMERG "arr[%d] = %d\n", i, arr[i]);
	}

	return 0;
}

void __exit module_param_exit(void)
{
	printk(KERN_EMERG "bye ~\n");

	return ;
}

module_init(module_param_init);
module_exit(module_param_exit);

【4】编写Makefile

vim Makefile

bash 复制代码
obj-m += module_param.o
KERNEL_PATH=/home/zjd/s5p6818/KERNEL/kernel
ROOTFS_PATH=/nfs_share/_install

all:
	make -C $(KERNEL_PATH) M=$(PWD) modules
	cp *.ko $(ROOTFS_PATH)

clean:
	make -C $(KERNEL_PATH) M=$(PWD) clean

【5】下位机验证

模板参数在系统中有负责维护的节点

/sys/module/module_param/parameters/

【6】思考

我们在驱动代码里面定义了3个全局变量,这里只出来两个,arr、irq

因为我们将str全局变量的权限设定为0,所以没有显示

我们给irq与arr全局变量设定的权限就是该节点文件的权限

【7】意义

我们可以在内核中验证寄存器的功能,将寄存器声明为模块参数,在安装模块的时候就可以使用模块参数(REG=VAL)

a、系统调用

系统调用是用户进入内核空间的一种方式(还可以通过中断进入内核空间)

1)意义

【1】用户空间到内核空间

是用户空间调用内核空间函数的一种方式

【2】安全

系统调用保证了内核的安全,允许应用程序调用内核中的函数(以安全的方式)

2)系统调用的实现

1】应用程序首先使用适当的值,填充寄存器

2】调用特殊的指令

3】执行指令,跳转到某个位置

4】在该位置,根据填充到寄存器的值,找到内核中对应的函数

5】调用该函数

6】函数执行完毕后,原路返回到用户空间

3)验证

【1】应用程序选取适当的值

vim kernel/arch/arm/include/asm/unistd.h

【2】填充寄存器

vim kernel/arch/arm/kernel/entry-common.S

寄存器:r7

【3】调用特殊指令

vim kernel/arch/arm/kernel/entry-common.S

检查系统调用号

去系统调用表中的宏进行匹配

输出调用号,跳转到asm_syscall()

vim kernel/arch/arm/kernel/traps.c

vim arch/arm/kernel/calls.S

4)演示

【1】新增一个系统调用号

vim kernel/arch/arm/include/asm/unistd.h

【2】新增一个内核中的API(接口)函数

vim kernel/arch/arm/kernel/sys_arm.c

【3】修改系统调用表

vim kernel/arch/arm/kernel/call.S

【4】重新编译内核

make uImage

【5】重新烧录内核

cp arch/arm/boot/uImage /tftpboot

tftp 48000000 uImage

mmc write 48000000 2000 3000

re

【6】系统调用syscall

【7】编写测试程序

mkdir sys_call

vim sys_add.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>

#define SYS_ADD_NUM		378

int main(void)
{
	int ret = 0;

	ret = syscall(SYS_ADD_NUM, 20, 6);

	printf("ret = %d\n", ret);

	return 0;
}

【8】编译sys_add.c文件

arm-cortex_a9-linux-gnueabi-gcc sys_add.c -o sys_add

【9】下位机测试

cp sys_add /nfs_share/_install/

【a】调试

1】查找交叉编译链工具位置

which arm-cortex_a9-linux-gnueabi-gcc

2】进入gcc的库目录

cd /opt/toolchains/arm-cortex_a9-eabi-4.7-eglibc-2.18/arm-cortex_a9-linux-gnueabi/lib

3】拷贝需要的库文件根文件系统

cp libgcc_s.so.1 /nfs_share/_install/lib/

【b】再次测试

相关推荐
内核程序员kevin2 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
朝九晚五ฺ7 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
自由的dream7 小时前
Linux的桌面
linux
xiaozhiwise7 小时前
Makefile 之 自动化变量
linux
Kkooe8 小时前
GitLab|数据迁移
运维·服务器·git
久醉不在酒8 小时前
MySQL数据库运维及集群搭建
运维·数据库·mysql
意疏9 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
虚拟网络工程师9 小时前
【网络系统管理】Centos7——配置主从mariadb服务器案例(下半部分)
运维·服务器·网络·数据库·mariadb
BLEACH-heiqiyihu9 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器