嵌入式内核系统结构
1. 内核启动流程
2. 系统调用
* 应用层想与内核层交互,要通过系统调用,应用层不能直接访问内核的内存
* 内核层驱动--->提供接口(绑定在某些系统调用的功能下)---
-->别人调用时-->通过系统调用的接口绑定模块--->实现具体功能
* 钩子 -- 把系统调用传递到内核层的某个模块的一条路勾走
3. 进程管理系统
4. 内存管理系统(MMU)
5. 字符设备驱动(按字节为单位读取)
输入子系统
V4L2子系统
USB总线系统
misc系统
ADSL音频系统
fb_mem 视频缓存系统
6. 块设备驱动(选择目标所在的一块,把它放进内存,再选择要读的目标)
7. 内核网络
netfilter 监控层
内核驱动设计方法(LKM)
- LKM : Lodaing Kernel Moudle 可加载内核模块设计
- 入口函数 : module_init()
- static int __init xxx(void)
为什么使用 static ?
因为 static 在 C 语言里可以限定它的作用域,
从内核>角度来说,很容易发生重名,对于这种
重名函数,为了不>对它的命名空间作污染,
所以给定义的函数用来限定在一个作用域有效
为什么加 __init / __exit?
可加可不加,
linux为了方便管理所有驱动的入口/出口函数,linux会指定专门的内存区域把内核的所有驱动存到这个专门的区域里,即这个指定区域里都是驱动的指针和地址
- 出口函数 : module_exit()
- static void __exit xxx(void)
- 驱动常用函数
内核是否能用 glic 的函数(标准C库)?
不能,
因为内核启动的时候,所有的库还没有调用/启动起来,库是存在文件系统里,而文件系统在内核启动之后才挂接的,内核无法直接访问文件系统中的库,所以内核是没办法使用文件系统里的库的,所有的内核函数自己实现的
-
printk(KERN_INFO "Hello World enter \n");
- 在kernel.h里,有调试级别比如在 printk 中要带调试级别
- 优先级 : 0 -7 MAX 0, MINI 7
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
-
kzmalloc ()
-
MODULE_AUTHOR() //指定模块的作者是谁
-
MODULE_LICENSE("GPL v2")
//指定模块的license,很固定
-
MODULE_DESCRIPTION() //模块详细描述
-
MODULE_ALIAS() //简要描述
-
如何传参 : 给内核驱动传参
-
module_param(book_name,charp,S_IRUGO)
-
第一个是传入参数的变量
-
第二个是 第一个参数的数据类型
-
第三个关于内核文件的权限值
-
S_IRUGO相关的参数,一般为S_IRUGO,只给用户可读
#ifndef _LINUX_STAT_H
#define _LINUX_STAT_H
#ifdef __KERNEL__
#include <asm/stat.h>
#endif
#if defined(__KERNEL__) || !defined(__GLIBC__) || (__GLIBC__ < 2)
#define S_IFMT 00170000
#define S_IFSOCK 0140000
#define S_IFLNK 0120000
#define S_IFREG 0100000
#define S_IFBLK 0060000
#define S_IFDIR 0040000
#define S_IFCHR 0020000
#define S_IFIFO 0010000
#define S_ISUID 0004000
#define S_ISGID 0002000
#define S_ISVTX 0001000
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
#endif
#ifdef __KERNEL__
#define S_IRWXUGO (S_IRWXU|S_IRWXG|S_IRWXO)
#define S_IALLUGO (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO)
#define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH)
#define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH)
#define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH)
-
book.c文件
#include <linux/init.h>
#include <linux/module.h>
static char *book_name = "dissecting Linux Device Driver";
module_param(book_name,charp,S_IRUGO);
static int book_num = 4000;
module_param(book_num, int ,S_IRUGO);
static int __init book_init(void)
{
printk(KERN_INFO "BOOK name :%s\n",book_name);
printk(KERN_INFO "BOOK num :%d\n",book_num);
return 0;
}
module_init(book_init);
static void __exit book_exit(void)
{
printk(KERN_INFO "book module exit\n ");
}
module_exit(book_exit);
MODULE_AUTHOR("tanzhou EDU");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple testing Module");
MODULE_ALIAS("a simplest module");
MODULE_VERSION("V1.0");
- 默认不给 .ko文件传参
- 给 .ko 文件传参
- 如何定义各类函数 :
- 如何加载 :
-
insmod xxx.ko
- 如何卸载 :
-
rmmod xxx.ko
- 如何调用 :
<1> dmesg 查看内核有什么
<2> 调用在xxx.ko 里使用module_init /
module_exit修饰的函数
- make 完后生成 . ko文件
- 切换 root权限
- 用 insmod hello.ko
- 用dmesg -c 查看内核打印的内容
- make 完后生成 . ko文件
- 用 rmmod hello.ko 卸载
- 再用 dmesg -c 查看
- 有时候执行上面 2 步操作后查看不到内核的打印的内容
- 这是缓存的问题
- 用 insmod hello.ko 重新加载,再用dmesg -c 查看即可
- 常用命令
-
modinfo xxx.ko //查看hello.ko的信息
-
uname -a //查看内核版本号,编译环境的内核要与实物一致就可以
- 调试方法:
如果内存写的不对,系统崩溃了,重启去做,要调试整个系统,因为内核要依靠系统来执行的,每个内核在加载系统中会有一个内核地址,加载之后,通过一个文件来读取这个地址,然后调试整个系统来断到这个地址上,然后调试刚加载自己加上的内核
-
查看LEDE 的内核版本
-
在lede里无法用uname -a 查看内核版本号,可在linux的 …/openwrt-17.01.4-17.01.4/package/kernel/linux/modules 里查看
-
下面用 other.mk 来作为例子创建内核驱动 (不需要改)
-
如何建立 Makefile && 编译方法
在.../openwrt-17.01.4-17.01.4/package/kernel/ 下创建文件夹 helloworld
- 随便取 I2C的 顶层Makefile 来作为模板使用
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=helloworld-kernel //改的名字
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define KernelPackage/helloworld-kernel //在make menuconfig里有用
SUBMENU:=$(OTHER_MENU) //在什么菜单生成
TITLE:= Helloworld kernel drive //题头
// DEPENDS:=@GPIO_SUPPORT +kmod-i2c-core +kmod-i2c-gpio //依赖
FILES:=$(PKG_BUILD_DIR)/helloworld.ko //生成的文件名
KCONFIG:=
AUTOLOAD:=$(call AutoProbe,81,helloworld) //81为装载的顺序,不写由系统定义
endef
define KernelPackage/helloworld-kernel/description
Kernel moudule for test
endef
// EXTRA_KCONFIG:= \
// CONFIG_I2C_GPIO_CUSTOM=m
// 内核在编译的时候有自己的config配置文件,有宏定义,引入到内核里去,没有就不写
EXTRA_CFLAGS:= \
$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=m,%,$(filter %=m,$(EXTRA_KCONFIG)))) \
$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=y,%,$(filter %=y,$(EXTRA_KCONFIG)))) \
MAKE_OPTS:= \
ARCH="$(LINUX_KARCH)" \
CROSS_COMPILE="$(TARGET_CROSS)" \
SUBDIRS="$(PKG_BUILD_DIR)" \
EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
$(EXTRA_KCONFIG)
define Build/Prepare //准备
mkdir -p $(PKG_BUILD_DIR)/
$(CP) -R ./src/* $(PKG_BUILD_DIR)
modules
endef
define Build/Compile
$(MAKE) -C "$(LINUX_DIR)" \
$(MAKE_OPTS) CONFIG_HELLOWORLD-KERNEL=m\
modules
endef
$(eval $(call KernelPackage,helloworld))
- 在 helloworld 文件夹下的src文件夹 创建Makefile
- 然后在 helloworld 文件夹下的src文件夹 创建 Kconfig
config HELLOWORLD-KERNEL
tristate "test kernel driver"
// depends on GENERIC_GPIO //依靠的文件
// select I2C_GPIO //如果需要依靠文件,要加上select来选择
help
This is an Kernel Driver Test
if unsure,delete it,just for test
- 然后把他们放进linux的 …/openwrt-17.01.4-17.01.4/package/kernel/ helloworld 下
- 这里的Kconfig一定要写,因为在编译内核文件的时候,编译器make会检测有没有这个文件
- make menuconfig 后选择 Kernel modules,会发现有下面的选项
- 选择 M,保存退出
- make ./package/kernel/helloworld/compile V=s
- 编译完成后在 /bin/packages/i386_entium4/packages
有内核的文件 - 在 staging_dir/target-i386_pentium4_musl-1.1.16/root-x86/lib/model里有 编译出来的 .ko 文件
- 然后上传到LEDE安装使用