Chinaunix首页 | 论坛 | 博客
  • 博客访问: 42212
  • 博文数量: 21
  • 博客积分: 71
  • 博客等级: 民兵
  • 技术积分: 110
  • 用 户 组: 普通用户
  • 注册时间: 2012-04-29 12:55
文章分类
文章存档

2013年(1)

2012年(20)

分类:

2012-09-05 21:38:06

原文地址:Linux体系结构 作者:luozhiyong131

 

 

 

 

Linux 户空间和内核空间两部分组成。

现代CPU通常实现了不同的工作模式,以ARM为例,实现了7种工作模式:

用户模式(usr)、快速中断(fiq)、外部中断(irq)、管理模式(svc)、数据访问中止(abt)、系统模式(sys)、未定义指令异常(und)

X86也实现了4个不同的级别:Ring0Ring3Ring0下,可以执行特权指令,可以访问IO设备等,在Ring3则有很多限制。

Linux系统利用了CPU的这一特性,使用了其中的两级来分别运行Linux内核与应用程序,这样使操作系统本身得到充分的保护。例如:如果使用X86,用户代码运行在Ring3,内核代码运行在Ring0

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

 

系统调用接口

SCI 层为用户空间提供了一套标准的系统调用函数来访问Linux

内核,搭起了用户空间到内核空间的桥梁。

进程管理

进程管理的重点是创建进程(forkexec),停止进程(killexit),并控制它们之间的通信(signal 或者 POSIX 机制)。进程管理还包括控制活动进程如何共享CPU,即进程调度。

内存管理

内存管理的主要作用是控制多个进程安全地共享内存区域。

网络协议栈

内核协议栈为Linux提供了丰富的网络协议实现。

虚拟文件系统(VFS

VFS隐藏各种文件系统的具体细节,为文件操作提供统一的接口。

设备驱动

Linux 内核中有大量代码都在设备驱动程序中,它们控制特定的硬件设备。

目录结构

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

 

目录结构

arch目录

archarchitecture的缩写。内核所支持的每种CPU体系,在该目录下都有对应的子目录。每个CPU的子目录,又进一步分解为boot,mm,kernel等子目录,分别包含控制系统引导,内存管理,系统调用等。

| --x86   /* 英特尔cpu及与之相兼容体系结构的子目录*/

| |--boot    /*引导程序*/

| | |--compressed   /*内核解压缩*/

| |--tools   /*生成压缩内核映像的程序*/

| |--kernel   /*相关内核特性实现方式,如信号处理、时钟处理*/

| |--lib   /*硬件相关工具函数*/

block目录

部分块设备驱动程序

crypto目录

加密、压缩、CRC校验算法

documentation

内核的文档

drivers目录

设备驱动程序

 fs目录

存放各种文件系统的实现代码。每个子目录对应一种文件系统的实现,公用的源程序用于实现虚拟文件系统vfs

| |--devpts /* /dev/pts虚拟文件系统*/

| |--ext2 /*第二扩展文件系统*/

| |--fat /*MSfat32文件系统*/

| |--isofs /*ISO9660光盘cd-rom上的文件系统*/

 include目录

内核所需要的头文件。与平台无关的头文件在include/linux 子目录下,与平台相关的头文件则放在相应的子目录中。

init目录

内核初始化代码

ipc目录

进程间通信的实现代码

kernel目录

Linux大多数关键的核心功能都是在这个目录实现。(调度程序,进程控制,模块化)

lib目录

库文件代码

mm目录

mm目录中的文件用于实现内存管理中与体系结构无关的部分(与体系结构相关的部分在哪里实现?)

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

* make distclean

mrproper + remove editor backup and patch files

2、确定目标系统的软硬件配置情况,比如CPU的类型、网卡的型号,所需支持的网络协议等。

3、使用如下命令之一配置内核:

 make config:基于文本模式的交互式配置。

 make menuconfig:基于文本模式的菜单型配置。(推荐使用)

make oldconfig

使用已有的配置文件(.config),但是会

询问新增的配置选项。

 make xconfig

图形化的配置(需安装图形化系统)。

make menuconfig 是最为常用的内核配置方式,使用方法如下:

1、使用方向键在各选项间移动;

2、使用“Enter”键进入下一层选单;每个选项上的高亮字母是键盘快捷方式,使用它可以快速地到达想要设置的选单项。

3、在括号中按“y”将这个项目编译进内核中,按“m”编译为模块,按“n”为不选择(按空格键也可在编译进内核、编译为模块和不编译三者间进行切换),按“h”将显示这个选项的帮助信息,按“Esc”键将返回到上层选单。

内核配置通常在一个已有的配置文件基础上,通过修改得到新的配置文件Linux内核提供了一系列可供参考的内核配置文件,位于Arch/$cpu/configs

4、编译内核:

make zImage

make bzImage

区别:在X86平台,zImage只能用于小于512K的内核

*如需获取详细编译信息,可使用:

make zImage V=1

make bzImage V=1

** 编译好的内核位于arch//boot/目录下 **

5、编译内核模块:

make modules

6、安装内核模块

make modules_install

**将编译好的内核模块从内核源代码目录copy/lib/modules**

7、制作init ramdisk

mkinitrd initrd-$version $version

例:

mkinitrd initrd-2.6.29  2.6.29

*$version 可以通过查询/lib/modules下的目录得到

内核安装(X86平台)

1cp arch/x86/boot/bzImage

/boot/vmlinuz-$version

2cp $initrd /boot/

3、修改/etc/grub.conf 或者 /etc/lilo.conf

** $version 为所编译的内核版本号**

 

内核模块

Linux内核的整体结构非常庞大,其包含的组件也非常多,如何使用需要的组件呢: 方法一:把所有的组件都编译内核文件,即:zImagebzImage,但这样会导致两个问题:一是生成的内核文件过大;二是如果要添加或删除某个组件,需要重新编译整个内核。

有没有一种机制能让内核文件(zImagebzImage)本身并不包含某组件,而是在该组件需要被使用的时候,动态地添加到正在运行的内核中呢?

Linux提供了一种叫做“内核模块”的机制,就可以实现以上效果。

内核模块具有如下特点:

* 模块本身并不被编译进内核文件(zImage或者bzImage)

* 可以根据需求,在内核运行期间动态的安装或卸载。

范例(hello world)

#include

#include

static int hello_init(void)

{

printk(KERN_WARNING"Hello, world !\n");

return 0;

}

static void hello_exit(void)

{

printk(KERN_INFO "Goodbye,  world\n");

}

module_init(hello_init);

module_exit(hello_exit);

 

程序结构

1、模块加载函数(必需)

安装模块时被系统自动调用的函数,通过module_init宏来指定,在HelloWorld模块中,模块加载函数为?

2、模块卸载函数(必需)

卸载模块时被系统自动调用的函数,通过module_exit宏来指定,在HelloWorld模块中,模块卸载函数为?

 

安装与卸载

加载 insmod (insmod hello.ko)

卸载 rmmod (rmmod hello)

查看 lsmod

加载 modprobe modprobe hello

modprobe 如同 insmod, 也是加载一个模块到内核。它的不同之处在于它会根据文件

/lib/modules/<$version>/modules.dep

来查看要加载的模块, 看它是否还依赖于其他模块,如果是,modprobe 会首先找到这些模块, 把它们先加载到内核。

对比应用程序,内核模块具有以下不同:应用程序是从头(main)到尾执行任务,执行结束后从内存中消失。内核模块则是先在内核中注册自己以便服务于将来的某个请求,然后它的初始化函数结束,此时模块仍然存在于内核中,直到卸载函数被调用,模块才从内核中消失。

1、许可证申明

MODULE_LICENSE用来告知内核, 该模块带有一个许可证,没有这样的说明,加载模块时内核会抱怨。有效的许可证有"GPL“、"GPL v2""GPL and additional rights""Dual BSD/GPL""Dual MPL/GPL""Proprietary"

2、作者申明(可选)

MODULE_AUTHOR(“Simon Li");

3、模块描述(可选)

MODULE_DESCRIPTION("Hello World 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)

 

内核符号导出

内核符号的导出使用:

EXPORT_SYMBOL(符号名)

EXPORT_SYMBOL_GPL(符号名)

其中EXPORT_SYMBOL_GPL只能用于包含GPL许可证的模块。

 

常见问题:版本不匹配

内核模块的版本由其所依赖的内核代码版本所决定, 在加载内核模块时,insmod程序会将内核模块版本与当前正在运行的内核版本比较,如果不一致时,就会出现类似下面的错误:

insmod hello.ko

disagrees about version of symbol struct_module

insmod: error inserting 'hello.ko': -1 Invalid module format

 

解决方法:

1、使用 modprobe  --force-modversion 强行插入

2、确保编译内核模块时,所依赖的内核代码版本等同于当前正在运行的内核。

**可通过uname r 察看当前运行的内核版本**

 

Printk是内核中出现最频繁的函数之一,通过将PrintkPrintf对比,将有助于大家理解。

相同点:

* 打印信息

不同点:

* Printk在内核中使用,Printf在应用程序中使用

* Printk允许根据严重程度,通过附加不同的“优先级”来对消息分类。

 

中定义了8种记录级别。按照优先级递减的顺序分别是:

KERN_EMERG                   “<0>”

用于紧急消息,常常是那些崩溃前的消息。

KERN_ALERT                   “<1>”

需要立刻行动的消息。

KERN_CRIT                     “<2>”

严重情况。

KERN_ERR                      “<3>”

错误情况。

* KERN_WARNING              “<4>”

有问题的警告

* KERN_NOTICE               “<5>”

正常情况,但是仍然值得注意

* KERN_INFO                 “<6>”

信息型消息

* KERN_DEBUG                “<7>”

用作调试消息

 

没有指定优先级的printk默认使用EFAULT_MESSAGE_LOGLEVEL优先级,它是一个在kernel/printk.c中定义的整数。在2.6.29内核中

#define DEFAULT_MESSAGE_LOGLEVEL 4

/* KERN_WARNING */

控制台优先级配置

/proc/sys/kernel/printk

6 4 1 7

* Console_loglevel

* Default_message_loglevel

* Minimum_console_level

* Default_console_loglevel

 

 

 

 

阅读(595) | 评论(0) | 转发(0) |
0

上一篇:C/C++ 编程规范

下一篇:Shell编程

给主人留下些什么吧!~~