Linux+Kernel学习笔记.docx

上传人:b****4 文档编号:4993082 上传时间:2022-12-12 格式:DOCX 页数:25 大小:32.36KB
下载 相关 举报
Linux+Kernel学习笔记.docx_第1页
第1页 / 共25页
Linux+Kernel学习笔记.docx_第2页
第2页 / 共25页
Linux+Kernel学习笔记.docx_第3页
第3页 / 共25页
Linux+Kernel学习笔记.docx_第4页
第4页 / 共25页
Linux+Kernel学习笔记.docx_第5页
第5页 / 共25页
点击查看更多>>
下载资源
资源描述

Linux+Kernel学习笔记.docx

《Linux+Kernel学习笔记.docx》由会员分享,可在线阅读,更多相关《Linux+Kernel学习笔记.docx(25页珍藏版)》请在冰豆网上搜索。

Linux+Kernel学习笔记.docx

Linux+Kernel学习笔记

LinuxKernel学习笔记

TableofContents

1.存储器寻址

2.设备驱动程序开发

3.字符设备驱动程序

3.1.设备号

3.2.设备号的分配和释放

3.3.重要的数据结构

3.4.读和写

4.PCI设备

5.内核初始化优化宏

6.访问内核参数的接口

7.内核初始化选项

8.内核模块编程

8.1.入门

8.2.为模块添加描述信息

8.3.内核模块处理命令介绍

9.网络子系统

9.1.sk_buff结构

9.2.sk_buff结构操作函数

9.3.net_device结构

9.4.网络设备初始化

9.5.网络设备与内核的沟通方式

9.6.网络设备操作层的初始化

9.7.内核模块加载器

9.8.虚拟设备

9.9.8139too.c源码分析

9.10.内核网络数据流

10.备忘录

Chapter 1. 存储器寻址

在80x86微处理器中,有三种存储器地址:

∙逻辑地址(logicaladdress),包含在机器语言指令中用来指定一个操作数或一条指令的地址。

每个逻辑地址都由一个段(segment)和一个偏移量(offset)组成。

偏移量指明了从段的开始到实际地址之间的距离。

∙线性地址(linearaddress)(也称为虚拟地址,virtualaddress),它是一个32位无符号整数,可用以表达高达4G的地址(2的32次方)。

通常以十六进制数表示,值的范围从0X00000000到0Xffffffff。

∙物理地址(physicaladdress),用于存储器芯片级存储单元寻址,它们与从微处理器的地址引脚发送到存储器总线上的电信号相对应。

物理地址由32位无符号整数表示。

CPU控制单元通过一种称为分段单元(segmentationunit)的硬件电路把一个逻辑地址转换成线性地址;线性地址又通过一个分页单元(pagingunit)的硬件电路把一个线性地址转换成物理地址。

逻辑地址由两部份组成,一个段标识符和一个指定段由相对地址的偏移量。

段标识符是一个16位长的字段,称为段选择符(segmentselector),偏移量是一个32位长的字段。

处理器提供专门的段寄存器以快速处理段选择符,段寄存器的唯一目的就是存放段选择符。

共有6个段寄存器,分别是cs、ss、ds、es、fs和gs。

其中cs、ss、ds寄存器有专门的用途。

∙cs是代码段寄存器,指向包含程序指令的段。

∙ss是栈寄存器,指向包含当前程序栈的段。

∙ds是数据段寄存器,指向包含静态数据或者外部数据的段。

cs寄存器有一个重要功能,它包含有一个两位的字段,用以指明CPU当前特权级别(CurrentPrivilegeLevel,CPL)。

值0表示最高优先级,值3表示最低优先级。

Linux只用到0级和3级,分别表示内核态和用户态。

每个段由一个8字节的段描述符表示,它描述了段的特征。

段描述符放在全局描述符表(GlobalDescriptorTable,GDT)中或局部描述符表(LocalDescriptorTable,LDT)中。

段描述符的组成:

∙32位的Base字段,含有段的第一个字节的线性地址。

∙粒度标记G。

如果该位清0,则段大小以字节为单位,否则以4096字节的倍数计。

∙20位的Limit字段指定段的长度(以字节为单位,Limit字段为0的段被认为是空段)。

当G为0时,段的大小在1字节到1MB之间;否则段的大小在4KB到4GB之间。

∙系统标记S。

如果它被清0,则这是一个系统段,用于存储内核数据结构,否则,它是一个普通的代码段或数据段。

∙4位Type字段,描述段的类型和它的访问权限。

常用的Type有以下几种:

o代码段描述符

o数据段描述符

o任务状态段描述符

o局部描述符表描述符

Chapter 2. 设备驱动程序开发

在编程思路上,机制表示需要提供什么功能,策略表示如何使用这些功能。

区分机制和策略是UNIX设计最重要和最好的思想之一。

如X系统就由X服务器和X客户端组成。

X服务器实现机制,负责操作硬件,给用户程序提供一个统一的接口。

而X客户端实现策略,负责如何使用X服务器提供的功能。

设备驱动程序也是机制与策略分离的典型应用。

在编写硬件驱动程序时,不要强加任何特定的策略。

Linux系统将设备分成三种类型,分别是字符设备、块设备和网络接口设备。

在linux中通过设备文件访问硬件,设备文件位于/dev目录下。

设备文件是一种信息文件,普通文件的目的在于存储数据,设备文件的目的在于向内核提供控制硬件的设备驱动程序的信息。

设备文件保存了多种信息,其中重要的有设备类型信息,主设备号(major),次设备号(minor)。

主设备号与次设备号起到连接应用程序和设备驱动程序的作用。

当应用程序利用open()函数打开设备文件时,内核从相应的设备文件中得到主设备号,从而查找到相应的设备驱动程序,由次设备号查找实际设备。

所以主设备号对应设备驱动程序,次设备号对应由该驱动程序所驱动的实际设备。

通过设备文件可以向硬件传送数据,也可从硬件接收数据。

设备文件使用mknod命令生成。

mknod命令语法如下:

mknod[设备文件名][设备文件类型][主设备号][次设备号]

字符设备用c表示,块设备用b表示,网络设备没有专门的设备文件。

读写设备文件时要使用低级输入输出函数,不要使用带缓冲的以f开头的流文件输入输出函数。

但并不是所有低级输入输出函数都可以用在设备文件上,可以用在设备文件的低级输入输出函数有以下几个:

open()打开文件或设备

close()关闭文件

read()读取数据

write()写数据

lseek()改变文件的读写位置

ioctl()实现read(),write()外的特殊控制,该函数只在设备文件中使用

fsync()实现写入文件上的数据和实际硬件的同步

Chapter 3. 字符设备驱动程序

TableofContents

3.1.设备号

3.2.设备号的分配和释放

3.3.重要的数据结构

3.4.读和写

3.1. 设备号

字符设备在系统中以设备文件的形式表示,位于/dev目录下。

每个字符设备都有一个主设备号和次设备号,主设备号标识设备对应的驱动程序,次设备号标识设备文件所指的具体设备。

主次设备号的数据类型是dev_t,在/linux/types.h中定义。

在2.6内核中,dev_t是一个32位的数,其中12位用来表示主设备号,其余20位用来表示次设备号。

要获得设备的主次设备号可以使用内核提供的宏:

MAJOR(dev_tdev);#获得主设备号

MINOR(dev_tdev);#获得次设备号

这些宏定义位于linux/kdev_t.h中。

如果要把主次设备号转换成dev_t类型,则可使用:

MKDEV(intmajor,intminor);

3.2. 设备号的分配和释放

在建立一个字符设备之前,需要为它分配一个或多个设备号。

使用register_chrdev_region()函数完成设备号的分配。

该函数在linux/fs.h中声明。

原型如下:

intregister_chrdev_region(dev_tfirst,unsignedintcount,char*name);

first:

是要分配的主设备号范围的起始值,次设备号一般设置为0;

count:

是所请求的连续设备号的个数;

name:

是和该设备号范围关联的设备名称,它将出现在/proc/devices或/sysfs中。

如果分配成功则返回0,分配失败则返回一个负的错误码,所请求的设备号无效。

还有一个自动分配设备号的函数alloc_chrdev_region(),原型如下:

intalloc_chrdev_region(dev_t*dev,unsignedintfirstminor,unsignedintcount,char*name);

dev:

自动分配到设备号范围中的第一个主设备号;

firstminor:

自动分配的第一个次设备号,通常为0;

count:

是所请求的连续设备号的个数;

name:

是和该设备号范围关联的设备名称,它将出现在/proc/devices或/sysfs中。

如果我们不再使用设备号,则要使用unregister_chrdev_region()函数释放它。

函数原型如下:

voidunregister_chrdev_region(dev_tfirst,unsignedintcount);

函数的参数作用同上

我们一般在模块的清除函数中调用设备号释放函数。

在内核源码目录的Documentation/devices.txt文件中列出了已静态分配给常用设备的主设备号。

为了减少设备号分配的冲突,我们一般要使用alloc_chrdev_region()函数来自动分配设备号。

3.3. 重要的数据结构

文件操作结构:

structfile_operations,在linux/fs.h中定义。

它包含一组函数指针,实现文件操作的系统调用,如read、write等。

每个打开的文件都和一个文件操作结构关联(通过file结构中指向file_operations结构的f_op字段进行关联)。

文件结构:

structfile,在linux/fs.h中定义。

file结构代表一个打开的文件,由内核在open时创建。

指向文件结构的指针在内核中通常称为filp(文件指针)。

当文件的所有实例都被关闭之后,内核会释放这个数据结构。

节点结构:

structinode,在linux/fs.h中定义。

inode结构是内核表示文件的方法,而file结构是以文件描述符的方式表示文件的方法。

结构中以下两个字段对编写驱动程序有用:

∙dev_ti_rdev,该字段包含了真正的设备编号。

∙structcdev*i_cdev,该字段包含指向structcdev结构的指针。

从设备的inode获取主次设备号的宏:

unsignedintiminor(structinode*inode);

unsignedintimajor(structinode*inode);

3.4. 读和写

下面两个是字符设备读写操作最重要的内核函数。

unsignedlongcopy_to_user(void__user*to,constvoid*from,unsignedlongn);

读操作,把数据从内核空间复制到用户空间,返回不能复制的字节数,如果成功则返回0。

to目的地址,在用户空间中;

from源地址,在用户空间;

n要复制的字节数。

unsignedlongcopy_from_user(void*to,constvoid__user*from,unsignedlongn);

写操作,把数据从用户空间复制到内核空间,返回不能复制的字节数,如果成功则返回0。

to目的地址,在内核空间中;

from源地址,在用户空间;

n要复制的字节数。

Chapter 4. PCI设备

pci设备上电时,硬件保持未激活状态。

设备不会有内存和I/O端口映射到计算机的地址空间。

每个PCI主板上都配备有能够处理PCI的BIOS、NVRAM或PROM等固件。

这些固件通过读写PCI控制器中的寄存器,提供了对设备配置地址空间的访问。

系统引导时,固件在每个PCI设备上执行配置事务,以便为它提供的每个地址区域分配一个安全的位置。

当驱动程序访问设备时,它的内存和I/O区域已经被映射到了处理器的地址空间。

所有PCI设备都有至少256字节的地址空间。

前64字节是标准化的,每种设备都有且意义相同,其余字节是设备相关的。

在内核中有三个主要的数据结构与PCI接口有关,在开发PCI设备驱动程序时要用到,分别是:

∙pci_device_id,PCI设备类型的标识符。

在include/linux/mod_devicetable.h头文件中定义。

∙structpci_device_id{

∙__u32vendor,device;/*VendoranddeviceIDorPCI_ANY_ID*/

∙__u32subvendor,subdevice;/*SubsystemID'sorPCI_ANY_ID*/

∙__u32class,class_mask;/*(class,subclass,prog-if)triplet*/

∙kernel_ulong_tdriver_data;/*Dataprivatetothedriver*/

∙};

PCI设备的vendor、device和class的值都是预先定义好的,通过这些参数可以唯一确定设备厂商和设备类型。

这些PCI设备的标准值在include/linux/pci_ids.h头文件中定义。

pci_device_id需要导出到用户空间,使模块装载系统在装载模块时知道什么模块对应什么硬件设备。

宏MODULE_DEVICE_TABLE()完成该工作。

设备id一般用数组形式。

如:

staticstructpci_device_idrtl8139_pci_tbl[]={

{0x10ec,0x8139,PCI_ANY_ID,PCI_ANY_ID,0,0,RTL8139},

....

};

MODULE_DEVICE_TABLE(pci,rtl8139_pci_tbl);

∙pci_dev,标识具体的PCI设备实例,与net_device类似。

内核通过该内核结构来访问具体的PCI设备。

在include/linux/pci.h头文件中定义。

∙pci_driver,设备驱动程序数据结构,它是驱动程序与PCI总线的接口,有大量的回调函数和指针,向PCI核心描述了PCI驱动程序。

在include/linux/pci.h头文件中定义。

∙staticstructpci_driverrtl8139_pci_driver={

∙.name=DRV_NAME,#设备名

∙.id_table=rtl8139_pci_tbl,#pci设备的id表组

∙.probe=rtl8139_init_one,#初始化函数

∙.remove=__devexit_p(rtl8139_remove_one),#退出函数

∙#ifdefCONFIG_PM#如果设备支持电源管理

∙.suspend=rtl8139_suspend,#休眠

∙.resume=rtl8139_resume,#从休眠恢复

∙#endif/*CONFIG_PM*/

∙};

内核通过pci_register_driver和pci_unregister_driver函数来注册和注消PCI设备驱动程序。

这两个函数在drivers/pci/pci.c源码中定义。

pci_register_driver函数需要使用pci_driver数据结构作为参数。

通过注册,PCI设备就与PCI设备驱动程序关联起来了。

PCI设备最大的优点是可以自动探测每个设备所需的IRQ和其它资源。

有两种探测方式,一种是静态探测,一种是动态探测。

静态探测是通过设备驱动程序自动选择相关资源,动态探测是指支持热插拔设备的功能。

PCI设备通过pci_driver结构中的suspend和resume函数指针支持电源管理。

可实现暂停和重新启动PCI设备的功能。

/lib/modules/KERNEL_VERSION/modules.pcimap文件列出内核所支持的所有PCI设备和它们的模块名。

debian:

/lib/modules/2.6.23.9#catmodules.pcimap|more

#pcimodulevendordevicesubvendorsubdeviceclassclass_maskdriver_data

snd-trident0x000010230x000020000xffffffff0xffffffff0x000401000x00ffff000x0

snd-trident0x000010230x000020010xffffffff0xffffffff0x000000000x000000000x0

...

8139cp0x000010ec0x000081390xffffffff0xffffffff0x000000000x000000000x0

8139cp0x000003570x0000000a0xffffffff0xffffffff0x000000000x000000000x0

...

Chapter 5. 内核初始化优化宏

内核使用了大量不同的宏来标记具有不同作用的函数和数据结构。

如宏__init、__devinit等。

这些宏在include/linux/init.h头文件中定义。

编译器通过这些宏可以把代码优化放到合适的内存位置,以减少内存占用和提高内核效率。

下面是一些常用的宏:

∙__init,标记内核启动时使用的初始化代码,内核启动完成后不再需要。

以此标记的代码位于.init.text内存区域。

它的宏定义是这样的:

∙#define__init__attribute__((__section__(".text.init")))

∙__exit,标记退出代码,对于非模块无效。

∙__initdata,标记内核启动时使用的初始化数据结构,内核启动完成后不再需要。

以此标记的代码位于.init.data内存区域。

∙__devinit,标记设备初始化使用的代码。

∙__devinitdata,标记初始化设备数据结构的函数。

∙__devexit,标记移除设备时使用的代码。

∙xxx_initcall,一系列的初始化代码,按降序优先级排列。

初始化代码的内存结构

 

_init_begin-------------------

|.init.text|----__init

|-------------------|

|.init.data|----__initdata

_setup_start|-------------------|

|.init.setup|----__setup_param

__initcall_start|-------------------|

|.initcall1.init|----core_initcall

|-------------------|

|.initcall2.init|----postcore_initcall

|-------------------|

|.initcall3.init|----arch_initcall

|-------------------|

|.initcall4.init|----subsys_initcall

|-------------------|

|.initcall5.init|----fs_initcall

|-------------------|

|.initcall6.init|----device_initcall

|-------------------|

|.initcall7.init|----late_initcall

__initcall_end|-------------------|

||

|.........|

||

__init_end-------------------

初始化代码的特点是:

在系统启动运行,且一旦运行后马上退出内存,不再占用内存。

对于驱动程序模块来说,这些优化标记使用的情况如下:

∙通过module_init()和module_exit()函数调用的函数就需要使用__init和__exit宏来标记。

∙pci_driver数据结构不需标记。

∙probe()和remove()函数应该使用__devinit和__devexit标记,且只能标记probe()和remove()

∙如果remove()使用__devexit标记,则在pci_driver结构中要用__devexit_p(remove)来引用remove()函数。

∙如果你不确定需不需要添加优化宏则不要添加。

Chapter 6. 访问内核参数的接口

内核通过不同的接口向用户输出内核信息。

我们可通过这些接口访问和修改内核参数。

共有三种接口,其中两种是procfs和sysfs虚拟文件系统,第三种是sysctl命令。

∙启用procfs虚拟文件系统的内核选项是"Filesystems-->Pseudofilesystems-->procfilesystemsupport"。

procfs文件系统挂载在/proc目录,可用cat、more等shell命令查看目录中的文件。

∙sysctl命令也可以修改和查看内核变量,sysctl操作的内核变量位于/proc/sys目录下。

启用sysctl支持的内核选项是"Generalsetup-->Sysctlsupport"。

∙procfs和sysctl接口已使用多年,从2.6内核开始,引入新的sysfs虚拟文件系统,它挂载在/sys目录下。

启用sysfs的内核选项是"Filesystems-->Pseudofilesystems-->sysfsfilesystemsupport(NEW)"。

sysfs以更整齐更直观的方式向用户展示了内核的各种参数。

/proc将会向sysfs迁移。

另外,通过ioctl(input/outputcontrol)systemcall和Netlink接口也可以向内核发送命令,执行内核参数配置工作,大多数的网络配置参数都可以用这两个接口修改。

ifconfig和route命令使用ioctl接口,IPROUTE2使用Netlink接口。

网络的ioctl命令在include/linux/sockios.h中定义。

这些命令被定义成类似于SIOCSIFMTU的宏,宏的命令规则是这样的,开头四个字符SIOC代表ioctl命令;S表示set,G表示get;if表示接口类型;MTU表示mtu。

其它字符的表示方式还有:

ADD表示添加,RT表示路由等。

Chapter 7. 内核初始化选项

我们可以通过内核初始化选项,在系统启动时或内核模块加载时微调内核的功能。

模块的初始化选项是通过模块程序中的module_param宏传递的。

如:

...

module_param(multicast_filter_limit,int,0444);

module_param(max_interrupt_work,int,0444);

module_param(debug,int,0444);

...

module_param宏的第一

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 求职职场 > 简历

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1