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-Ring3Ring0下,可以执行特权指令,可以访问IO设备等,Ring3下则有更多的限制。Linux系统利用了CPU的这一特性,使用了其中的两来分别运行Linux内核与应用程序,这样使操作系统本身得到充分的保护。

     b.ARM实现了7种工作模式:

用户模式(usr)、快速中断(fiq)、外部中断(irq)、管理模式(svc)、数据访问中止

(abt)、系统模式(sys)、未定义指令异常(und)内核运行的等级要高,保证了系统的稳定


内核空间与用户空间是程序执行的两种不同的状态,通过[系统调用]和[硬件中断]能够完成从用户空间到内核空间的转移。


二、linux内核架构



 linux 体系结构图(二)

系统调用接口:SCI层为用户空间提供了一套[标准的系统调用函数]来访问Linux内核,搭起了用户空间到内核空间的桥梁。
进程管理:它的重点是创建进程(forkexec),停止进程(killexit),并控制它们之间的通信(signalPOSIX机制)。进程管理还包括控制活动进程如何共享CPU,即[进程调度]
内存管理:主要作用是控制多个进程安全地共享内存区域。
ARCH:architecture
。内核支持的每种CPU体系
设备驱动程序
网络协议栈:内核协议栈为Linux提供了丰富的网络协议栈实现。
虚拟文件系统:VSF隐藏各个文件系统的具体细节,为文件操作提供统一的接口。

三、Linux kernel目录结构


Linux内核源代码采用树形结构进行组织,非常合理地[把功能相关的文件都放在同一个子目录下],使得程序更具可读性。

arch目录:内核所支持的每种CPU体系,在该目录下都有对应的子目录。每个CPU的子目录,又进一步分解为bootmmkernel等子目录。
|--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
协议族V432位寻址模式*/
||--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
内核的整天结构非常庞大,其包含的组件也非常的多,如何使用需
要的组件:
方法一:把所有的组件都编译进内核文件,即:zImagebzImage
这样导致两个问题:
a:生成的内核文件过大;
b:如果要添加或删除某个组件,需要重新编译整个内核.

方法二:内核模块:

让内核文件(zImagebzImage)本身并不包含某个组件,而是在该组件需要被使用时,[动态地添加到正在运行的内核里]

内核模块的特点:
模块本身并不被编译进内核文件(zImagebzImage)可以根据需求,在内核运行期间动态的安装或卸载

范例一:

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.cmakefile)

    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
intcharp(字符串型)
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有优先级
函数printkLinux内核中定义,功能和标准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,即可关注。

0 0
原创粉丝点击