Linux 内核简介
来源:互联网 发布:mac提示flash过期 编辑:程序博客网 时间:2024/06/18 00:47
转载请注明出处:http://blog.csdn.net/se7chen/article/details/44132857
某天,研发老大们说:“公司今年也要着手智能手机产品研发,这对软件,硬件,结构,质量等部门都有相当大的挑战,特别是软件部 ,因为公司目前确实没有太多android开发经验的工程师,....此处省略1万字 ", 会上老大让我负责android驱动相关培训和后面智能机项目驱动的预研工作。当时的我又喜又愁。喜的是终于可以揭开android智能机开发的神秘面纱,愁的是如何去揭开?闲话这么多,有点脱离今天的主题了,回到今天的主角 linux,它为什么会跑进我们android 智能机研发的培训计划当中呢? 原因很简单Android是一种以Linux为基础的开放源码操作系统。可想而知今天学习的linux内核驱动课程,就是在扎马步,打基础。开始今天的课程之前,我希望大家带着下面一些问题完成此次的阅读之旅,这样不至于睡着了。
1.linux 由用户空间和内核空间两部分组成,为什么如此设计? 用户空间和内核空间是如何通信的?
2.Makefile、 .config 、 Kconfig之间的关系?
3.设备号是做什么的? 添加字符设备流程?
开启学习模式:
linux体系结构
linux内核架构
linux内核目录结构
内核配置与编译
内核模块开发
字符设备
一、linux体系结构
linux 体系结构图(一)
由上图可知,Linux由用户空间和内核空间
(1)用户空间:用户空间中又包含了,用户的应用程序,C库
(2)内核空间:内核空间包括,系统调用,内核,以及与平台架构相关的代码
两部分组成。为什么这样设计?
a.X86实现了4个不同的级别:Ring0-Ring3。Ring0下,可以执行特权指令,可以访问IO设备等,Ring3下则有更多的限制。Linux系统利用了CPU的这一特性,使用了其中的两层来分别运行Linux内核与应用程序,这样使操作系统本身得到充分的保护。
b.ARM实现了7种工作模式:
用户模式(usr)、快速中断(fiq)、外部中断(irq)、管理模式(svc)、数据访问中止
(abt)、系统模式(sys)、未定义指令异常(und)内核运行的等级要高,保证了系统的稳定。
内核空间与用户空间是程序执行的两种不同的状态,通过[系统调用]和[硬件中断]能够完成从用户空间到内核空间的转移。
linux 体系结构图(二)
系统调用接口:SCI层为用户空间提供了一套[标准的系统调用函数]来访问Linux内核,搭起了用户空间到内核空间的桥梁。
进程管理:它的重点是创建进程(forkexec),停止进程(kill、exit),并控制它们之间的通信(signal、POSIX机制)。进程管理还包括控制活动进程如何共享CPU,即[进程调度]。
内存管理:主要作用是控制多个进程安全地共享内存区域。
ARCH:architecture。内核支持的每种CPU体系
设备驱动程序
网络协议栈:内核协议栈为Linux提供了丰富的网络协议栈实现。
虚拟文件系统:VSF隐藏各个文件系统的具体细节,为文件操作提供统一的接口。
三、Linux kernel目录结构
Linux内核源代码采用树形结构进行组织,非常合理地[把功能相关的文件都放在同一个子目录下],使得程序更具可读性。
arch目录:内核所支持的每种CPU体系,在该目录下都有对应的子目录。每个CPU的子目录,又进一步分解为boot,mm,kernel等子目录。
|--x86 /*英特尔cpu及与之相兼容体系结构的子目录*/
||--boot /*引导程序*/
|||--compressed /*内核解压缩*/
||--tools /*生成压缩内核映像的程序*/
||--kernel /*相关内核特性实现方式,如信号处理,时钟处理*/
||--lib /*硬件相关工具函数*/
block目录:部分块设备驱动程序
crypto目录:加密、压缩、CRC校验算法
documentation:内核文档
drivers目录:设备驱动程序
fs目录:存放各种文件系统的实现代码。每个子目录对应一种文件系统的实现,公用的源程序用于实现[虚拟文件系统vfs]
||--devpts /*/dev/pts虚拟文件系统*/
||--ext2 /*第二扩展文件系统*/
||--fat /*MS的fat32文件系统*/
||--isofs /*ISO9660光盘cd-rom上的文件系统*/
include目录:内核所需要的头文件。与平台无关的头文件在/include/linux目录下,与平台相关的头文件在相应的子目录中
lib目录:库文件代码(不是指的是C库,在编译内核产生的库)
mm目录:用于实现内存管理中[与体系结构无关的部分,与体系结构有关的代码在arch目录里]
net目录:网络协议的实现代码
||--802 /*802无限通讯协议核心支持代码*/
||--appletalk /*与苹果系统联网的协议*/
||--ax25 /*AX25无限INTERNET协议*/
||--bridge /*桥接设备*/
||--ipv4 /*ip协议族V4版32位寻址模式*/
||--ipv6 /*ip协议族V6版*/
samples:内核编程的一些范例
scripts:配置内核的脚本
security:SElinux的模块
sound:音频设备的驱动程序
usr:cpio命令实现
virt:内核虚拟机
四、内核配置与编译
1、清除临时文件、中间文件和配置文件
make clean
remove most generated files but keep the config
make mrproper
remove all generated files + config files
makedistclean
mrproper + remove editor backup and patch file
2、确定目标系统的软硬件配置情况,比如CPU类型、网卡的型号等
3、配置内核:
make config
基于文本模式的交互式配置。
make menuconfig
基于文本模式的菜单配置。(推荐使用)
make oldconfig
使用已有的配置文件(.config),但会询问新增的配置选项
make xconfig
图形化配置
配置方法:
根据已有的内核配置文件(每个体系结构目录的configs目录里有很多配置文件)去修改,将你需要的配置文件拷贝到源码顶层目录,然后再去配置、修改
makemenuconfig最常用
1)、使用方向键移动;
2)、使用“Enter”键进入下一层选单
3)、yn m的含义
五、内核模块开发
模块功能:
Linux内核的整天结构非常庞大,其包含的组件也非常的多,如何使用需
要的组件:
方法一:把所有的组件都编译进内核文件,即:zImage或bzImage
这样导致两个问题:
a:生成的内核文件过大;
b:如果要添加或删除某个组件,需要重新编译整个内核.
方法二:内核模块:
让内核文件(zImage或bzImage)本身并不包含某个组件,而是在该组件需要被使用时,[动态地添加到正在运行的内核里]。
内核模块的特点:
模块本身并不被编译进内核文件(zImage或bzImage)可以根据需求,在内核运行期间动态的安装或卸载
范例一:
a.(hello.c)
#include<linux/init.h>
#include<linux/module.h>
staticint__init hello_init(void)
{
printk("Hello!\n");
return0;
}
staticvoid __exit hello_exit(void)
{
printk("Goodbye!\n");
}
module_init(hello_init);
module_exit(hello_exit);
上面程序结构:
1、模块加载函数(必须)
安装模块时系统自动调用的函数,通过module_init宏来指定
2、模块卸载函数(必须)
卸载模块时被系统自动调用的函数,通过module_exit宏来指定
b.内核模块makefile的编写:
1)、有一个源文件的内核模块的编写
范例(hello.c的makefile)
ifneq($(KERNELRELEASE),)
obj-m:= hello.o #根据具体需要这个要变
Else
KDIR:= /lib/modules/2.6.18.e15/build #这个要变
all:
make-C $(KDIR) M=$(PWD) modules
clean:
rm-f *.ko *.o *.mod.o *.mod.c *.sysmvers
Endif
2)、有多个源文件的内核模块编写(main.c add.c)
/*main.c*/
#include<linux/module.h>
#include<linux/init.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lexuexuele");
MODULE_DESCRIPTION("lexuexueleModule");
MODULE_ALIAS("asimplest module");
externintsub(inta,intb);
staticint__init hello_init()
{
printk("Helloinit!\n");
add(2,1);
return0;
}
staticvoid __exit hello_exit()
{
printk("<7>hello<0>exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
/*sub.c*/
int sub(inta,intb)
{
returna -b;
}
#####################Makefile##########################
ifneq($(KERNELRELEASE),)
obj-m := hello.o
hello-objs := main.oadd.o
else
KDIR :=/lib/modules/2.6.32-71.el6.i686/build #这个要变
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o*.mod.o *.mod.c *.symvers *.unsigned *.order
endif
Kconfig
在上级目录里的Kconfig文件增加
source"内核源码所在目录/文件所在目录/Kconfig"
如是在drivers下新加目录的话,则修改arch/arm/Kconfig
内核模块的安装与卸载:
加载 insmod(insmod hello.ko)
卸载 rmmod(rmmod hello.ko)
查看 lsmod
加载 modprobe(modprobe hello)
modprobe 如同insmod,也是加载一个模块到内核。它们的不同之处在于modprobe会根据文件/lib/modules/<$version>/modules.dep来查看加载的模块,看它是否还依赖于其他模块,如果是,modprobe会首先找到这些模块,把它们先加载到内核。
模块可选信息
1、许可证申明
宏MODULE_LICENSE用来告知内核,该模块带有一个许可证,没有这样的说明,加载模块时内核会抱怨。有效的许可证有“GPL"、“GPL2"、“GPLand additional rigths”、“DualBSD/GPL”、“DualMPL/GPL”和 “Proprietary"。
2、作者申明(可选)
MODULE_AUTHOR("ButBueatiful");
3、模块描述(可选)
MODULE_DESCRIPTION("Hello Module");
4、模块版本(可选)
MODULE_VERSION("V1.0");
5、模块别名(可选)
MODULE_ALIAS("a simple module");
6、模块参数
通过宏module_param指定模块参数,模块参数用于在加载模块时传递参数给模块。
module_param(name, type, perm);
name是模块参数的名称,type是这参数的类型,
perm是模块参数的访问权限。
type常见值:
bool,int,charp(字符串型)
perm常见值:
S_IRUGO:任何用户都对/sys/module中出现的该参数具有读权限
S_IWUSR:允许root用户修改/sys/module中出现的该参数
例:
int a = 3;
char *st;
module_param(a, int, S_IRUGO);
module_param(st, charp, S_IRUGO);
内核符号导出:
/proc/kallsyms记录了内核中所有导出的符号的名字与地址。
内核符号的导出使用:
EXPORT_SYMBOL(符号名)
EXPORT_SYMBOL_GPL(符号名)
其中EXPORT_SYMBOL_GPL只能用于包含GPL许可证的模块
常见问题:版本不匹配
内核模块的版本由其所依赖的内核代码版本所决定,在加载内核模块时,insmod程序会将内核模块与当前正在运行的内核模块版本比较,如果不一致时,就会出错.
解决方法:
1、使用modprobe--force-modversion强行加载内核模块
2、确保编译内核模块时,所依赖的内核代码版本与现在的相同
uname -r 命令可查看内核版本
2.6与2.4内核模块对比
2.6是.ko2.4是.o;.ko是由.o的进一步封装而成
总结---对比应用程序:
对比应用程序,内核模块具有一下不同:
应用程序是从头 main到尾执行任务,执行结束后从内存中消失。内核模块则是先在内核中注册自己以便服务于将来的某个请求,然后它的初始化函数结束,此时模块仍然存在与内核中,直到卸载函数被调用,模块才从内核中消失。
内核打印:
printk 与 printf区别在于:printk有优先级
函数printk在Linux内核中定义,功能和标准C库中的函数printf类似。内核需要自己单独的打印输出函数,这是因为它在运行时不能依赖于C库。模块能够调用printk是因为在insmod函数装入模块后,模块就连接到了内核,因此可以访问内核的共用符号(包括函数和变量)。
在<linux/kernel.h>中定义了8中记录级别。按照优先级递减的顺序是:
KERN_EMERG "<0>" 用于紧急事件消息,他们一般是系统崩溃之前提示的消息
KERN_ALERT "<1>" 用于需要立即采取动作的情况
KERN_CRIT "<2>" 临界状态,通常涉及严重的硬件或软件操作失败
KERN_ERR "<3>"用于报告错误状态,设备驱动程序会经常使用KERN_ERR来报告来自硬件的问题。
KERN_WARNING"<4>" 对可能出现问题的情况进行警告,但这类情况通常不会对系统造成严重问题。
KERN_NOTICE "<5>" 有必要进行提示的正常情形,许多与安全相关的状况用这个级别进行汇报。
KERN_INFO "<6>" 提示性信息,很多驱动程序在启动的时候以这个级别来打印他们找到的硬件信息。
KERN_DEBUG "<7>" 用于调试信息。
没有指定优先级的printk默认使用DEFAULT_MESSAGE_LOGLEVEL优先级,它是一个在kernle/printk.c中定义的整数。在
2.6.29内核中:
#define DEFAULT_MESSAGE_LOGLEVEL 4 /*KERN-WARNING*/
控制台优先级配置:
/proc/sys/kernel/printk
4 4 1 7
Console_loglevel
Default_message_loglevel
Minimum_console_level
Default_console_loglevel
在printk中显式地指定优先级的原因在于:具有默认优先级的消息可能不会输出在控制台上。这依赖于内核版本、klogd守护进程的版本以及具体的配置。
六、字符设备
字符设备是指在I/O传输过程中以字符为单位进行传输的设备,例如键盘,打印机等。请注意,以字符为单位并不一定意味着是以字节为单位,因为有的编码规则规定,1个字符占16比特,合2个字节。
#include<linux/cdev.h>
structcdev {
struct kobject kobj; //内核用于管理字符设备驱动
struct module *owner; //通常设为THIS_MODULE,用于防止驱动在使用中时卸载驱动模块
const structfile_operations *ops; //怎样操作(vfs)
structlist_head list; //因多个设备可以使用同一个驱动,用链表来记录
dev_t dev; //设备号
unsigned int count; //设备数
};
设备号是做什么的?
设备号分为主设备号和次设备号,主设备号是识别对应的驱动程序,次设备号确定设备文件指向的设备,cat/proc/devices查看设备号使用情况.
静态设备号:
获取设备号
#defineMAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#defineMINOR(dev) ((unsigned int) ((dev) & MINORMASK))
定义设备号
#defineMKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
静态设备号注册:
ntregister_chrdev_region(dev_t from, unsigned count, const char *name);
静态:申请指定的设备号,from指设备号,count指使用该驱动有多少个设备(次设备号),name设备名
设备号释放:
unregister_chrdev_region(dev_tfrom, unsigned count)
释放设备号,from指设备号,count指设备数
动态申请设备号:
intalloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
constchar *name)
//动态申请设备号,由内核分配没有使用的主设备号,分配好的设备存在dev,baseminor指次设备号从多少开始,count指设备数,name设备名
添加字符设备流程:
1)申请设备号
2)定义一个cdev的设备驱动对象
struct cdev mycdev;
定义一个file_operations的文件操作对象
structfile_operations fops = {
.owner =THIS_MODULE,
.read = 读函数
....
};
3).把fops对象与mycdev关联起来
cdev_init(&mycdev, &fops); //mycdev.ops = &fops;
mycdev.owner = THIS_MODULE;
4).把设备驱动加入内核里, 并指定该驱动对应的设备号
cdev_add(&mycdev, 设备号, 次设备号的个数);
5).卸载模块时, 要把设备驱动从内核里移除, 并把设备号反注册
cdev_del(&mycdev);
6).创建设备文件,有二种方式
a)手动:User运行mknod/dev/设备文件名c主设备号 次设备号
b)自动添加设备文件:
struct class *cl;
cl =class_create(owner, name) ; //owner指属于哪个模块,name类名
//创建出来后可以查看/sys/class/类名
void class_destroy(structclass *cls); //用于销毁创建出来的类
创建设备文件
struct device*device_create()
例如:
device_create(所属的类,NULL,设备号,NULL, "mydev"); //在/dev/目录下产生名字为mydev的设文件
七、杂设备:
在Linux系统中,存在一类字符设备,它
们共享一个主设备号(10),但次设备号
不同,我们称这类设备为混杂设备(miscdevice)。所有的混杂设备形成一个
链表,对设备访问时内核根据次设备号查
找到相应的miscdevice设备
Linux内核使用structmiscdevice来描述一个混杂设备。
structmiscdevice {
intminor; /*次设备号*/
constchar *name; /*设备名*/
conststruct file_operations *fops; /*文件操作*/
structlist_head list;
structdevice *parent;
structdevice *this_device;
};
Linux内核使用misc_register函数来注册
一个混杂设备驱动。
intmisc_register(struct miscdevice * misc)
设备号查看:
cat/proc/devices可查看设备使用情况
在内核源码的documentations/devices.txt可查看设备号的静态分配情况
叮叮叮,还在睡覺的亲们,该醒醒了,到这里暂时就结束吧,我写下来也特别枯燥。验证您学有所获的标准,希望通过之前提的三个问题去检验一下, 也许细心的您或许会发现文章并没解答第二个问题,第二个问题其实你可以到百度先去学习一下,为了照顾到要睡覺的亲们,只能在下次写博客再次说明。乐学乐祝您每天学习快乐,快乐学习,最后再次贴出三个问题,以达到文章首尾照应之目的,^_^!
1.linux 用户空间和内核空间两部分组成,为什么如此设计? 用户空间和内核空间是如何通信的?
2.Makefile、 .config 、 Kconfig之间的关系?
3.设备号是做什么的? 添加字符设备流程?
第一时间获得博客更新提醒,以及更多技术信息分享,欢迎关注我的微信公众号,扫一扫下方二维码或搜索微信号lexuexuele,即可关注。
- Linux 内核驱动简介
- Linux内核简介
- Linux内核简介
- Linux内核2.6简介
- 第一章Linux内核简介
- Linux 内核简介
- Linux 内核简介
- linux内核简介
- Linux内核目录简介
- Linux内核简介
- Linux内核简介
- Linux内核简介
- linux内核模块简介
- linux 内核简介
- Linux内核简介
- Linux 内核 简介
- Linux内核简介
- linux内核参数简介
- java学习笔记
- 关于D3D11,你必须了解的几件事情
- lua loadstring
- L-Talented Chef(数学题)
- 【Vim】无插件vim配置
- Linux 内核简介
- 模板声明与定义要放在同一文件中
- windowBuilder安装
- Eclipse 单步调试系列
- 大数据时代入门<一>——数据库与数据仓库
- springMVC原理图
- HashMap实现原理分析
- online_judge_1196
- 黑马程序员--java学习总结(8)