常见问题常见问题   搜索搜索   会员列表会员列表   团队团队   注册注册    个人资料个人资料   登录查看您的站内信件登录查看您的站内信件   登录登录 

FreeBSD核心探讨

 
发表新文章   这个论题已经被锁定,您不能发表、回复或者编辑文章。    FreeBSD China -> 中文-桌面-开发-调试
阅读上一个主题 :: 阅读下一个主题  
作者 留言
kinki
精神病


注册时间: 2002-06-10
文章: 682

文章发表于: Wed 2002-10-16 13:28:39    发表主题: FreeBSD核心探讨 引用并回复

作者:Lenn

Tel: 0755-3913403(H)

E-mail: Liangvy@bigfoot.com

Oicq: Lenn 28663

转载自网易广州社区Freebsd精华版

-------------------------------------------------







1.1概述

FreeBSD可以在PC/AT兼容机器上运行.CPU是i386,i486,Pentium,

Pentium Pro以及其兼容芯片等.

1.1.1(略)

1,理论地址: 2个13 bit 长+32 bit 长

2,线形地址:32 bit 长的空间

3,物理地址:32 bit 长的空间

1.1.2进程的虚拟空间

1,text部分

这部分是执行文件的的text领域,也就是机器语言部分,对于这个

部分的空间在机器上的物理内存页是共有的,还有,这部分最后的变量

地址是etext.

2,data和bss部分

执行文件的data部分,也就是初始化的数据段和执行文件指定的内

存变量.内存变量在开始的时候以0填充.这一段空间可以读写.它的

边界也是以edata和end的地址做结尾.进程的malloc()等内存分配的

操作的时候,地址的增加方向向bss空间进行.

3,stack部分

也就是进程执行的时候的stack空间,这部分空间(从地址的最高位

开始可以伸缩),其对于物理内存,伸缩程度由核心自动执行.

1.2kernel的configure

freebsd的kernel构成文件在/usr/src/sys的目录下面.下面的子目录做一个

介绍.

compile 编译核心的目录.

conf configure的目录.

ddb 核心调试的sounre code的目录.

dev 一部分的drivers的source code的目录.

gnu 浮点运算的仿真以及ex2fs文件系统的source code目录.

i386 依赖于pc/at机器的目录,以下介绍它的字目录.

apm suspend一些节电程序.

boot 不是kernel本身的东西,只是一些怎么从开机到读入kernel

的boot program的source code.

conf config的一些依赖data.

isa isa bus的驱动程序类的source code.

eisa eisa bus的驱动程序类的source code.

include 对pc/at的一些include files

i386 对pc/at的一些核心code

ibcs2,linux 使各类的os的执行文件在freebsd上执行的code

isofs/cd9660

cd-rom在unix文件系统上操作的的有关code

kern 核心code

libkern 核心库的source code

miscfs 实现unix文件系统的code

msdosfs 在unix上操作ms-dos文件系统的有关code

net 实现network功能的基本部分code

netatalk

实现appletalk network功能code

netinet 实现internet network功能的code

netipx 实现ipx功能的code

netns 实现ns network的code

netkey 实现网络加密部分的功能的code

nfs 实现nfs服务

pc98 对于pc98的支持

pccard 对pcmcia的支持

pci 对pci bus的驱动程序的source code

scsi 对cd-rom,hard disk,tape 等的scsi驱动程序的source code

sys 独立于机器体系结构的一部分code

ufs unix file system 的支持code

vm 虚拟内存管理的部分

1.2.1配置的操作----config command

在root权限下,config,make实行后,可以得到简单的kernel.

*configure file

移动到/usr/src/sys/i386/config看看.

GENERIC 从cd-rom等安装freebsd的时候对应于defaule kernel

的配置file

LINT kernel组合功能的网罗的的配置file

下面4个是对配置很有必要的的依赖data file

Makefile.386 config生成的Makefile file的template.

devices.i386 对于unix filesystem可能的block型的device

名字和major号的对照表

files.i386 记录kernel功能组合的基础上,依赖于pc/at

机器的功能名称和各种功能实现的source code

file的名字表.

options.i386 记录配置项目的表.

还有,majors.i386是记录对应驱动器的I/O表和major号的一个文件.

于核心配置没关系.

对于新的i/o设备,如果要做device driver,对pc/at,要在files.i386(没

有的话在/usr/src/sys/conf/files)追加相应的行,不然就不能把它加入

到核心里面.

追加的格式为

相对path名 optional device-name device-driver



对于配置文件,首先,要设置cpu,bus,i/o设备,多少用户等.例如对于GENERIC

machine "i386"

cpu "I386_CPU"

cpu "I486_CPU"

cpu "I586_CPU"

cpu "I686_COU"

ident GENERIC

maxusers 10

当作为server时候,应该把最大user设置大一点,以提高系统性能.

下一步,指定options,对于GENERIC

options MATH_EMULATE #support for x87 emulation

options INET #interNETworing

options FFS #Berkeley Fast Filesystem

options NFS #Network Filesystem

......

options指定的名字xxx等,如果在/usr/src/sys/conf/options或者在

/usr/src/sys/i386/conf/options.i386中记载的时候,应在对应的opt_XXX.h中写入

.没有的话,作为cc命令行的参数定义"-D"在Makefile里面追加.对于XXX的格式应该



相对path名 optional xxx

下一步,对于config

config kernel root on wd0

(略)

配置文件剩下的部分应该是bus,i/o等一些硬件配置,一般有controller,device,

disk,tape四类.例如

controller isa0

controller eisa0

controller pci0

等.

第二层的device和controller,记录了一些bus设备的连接.ISA的情况是

device device_name at isa? 参数

controller controller_name at isa? 参数

EISA和PCI就相对简单一点:

device device_name

controller controller_name

device_name里指定的设备名是,串口,并口,网络等装置.

第三层的disk和tape为

disk disk_name at 控制设备名 drive 号

tape tape_name at 控制设备名 drive 号

SCSI接口卡作为第二层的控制装置记录的同时

controller scbus0

作为通用的scsi控制设备.因此,对于它的hard disk,tape,cd-rom,mo设备,有

device sd0

device st0

device cd0

device od0

等,它可以自动识别和分配号码.

对于其他的scsi设备,有

device pt0 at scbus?

这些东西(bus,scsi,i/o),在生成的ioconf.c以及相应的include中有反映.

configure的最后,不是一些物理设备,而是kernel内部的一些软设置

pseudo-device 理论设备名

首先,要考虑以下两个设备:

pseudo-device pty 16 #ttys - can go as high as 256

pseudo-device log #syslog interface (/dev/klog)

network使用的场合,应该有下面两个

pseudo-device loop

pseudo-device ether

这种情况下,最好有

pseudo-device bpfilter 4 #berkeley packet filter

pseudo-device tun 1 #Tunnel driver ( PPP)

想做floppy的时候,要

pseudo-device vn #Vnode driver ( turns a file into a device)

1.3FreeBSD boot之前的工作

1.3.1pc/at机器的boot顺序

hard disk的最前面的一个block(512byte),叫做master boot recorder(MBR).这

里有启动限定的program和分区的信息.分区信息是指对于一个区是16byte长,最多

只能有4个区.16byte的内容是,分区哪里开始,哪里结束.哪种os,能否启动等.对

于freebsd,安装的时候向MBR写入了boot easy.

磁盘的结构如下图表示:

block Number

#0 #1 #2 ... #14 #15 #16 #17

-------------------------------------------------------------

disk no used

label

-------------------------------------------------------------

<-boot->|<---------boot2-------------->| |<--unix file system--



FreeBSD用的block#0--#14的15个block里面,含有读入freebsd的程序,bootease

只在block#0里面,在15个block中并没有.它的作用

.读入mbr,找freebsd的分区

.读入最初的15个block,到物理内存中0x0001000

.跳转到相当于block#2的内存位置

然后,屏幕表示为:

...

...

boot:

(参数说明略)

它的source是/usr/src/sys/i386/boot/biosboot,make之后,生成两个文件:

boot1,boot2分别写入block#1,block#2--#14中.

一般,一个物理的unix分区理论上可以有8个,比如swap,unix system等.

boot2部分是boot program,它读入kernel的文件名和option.然后

.找boot label指定的分区.

.构造unix filesystem,找指定的kernel

.从开始执行文件,text,data的顺序向物理内存读入.对bss清零.

.以option的选择,向开始位置跳转.

1.3.2kernel的初始化动作

boot program执行之后,转向kernel的text段开始进行初始化,即先执行

locore.s的text段.因此是虚拟内存还没有发生作用,locore.s的开始部分必

须对offset进行补正.locore.s的作用是

.保存从boot program过来的option

.设定虚拟的stacker

.检测cpu的module

.对自己的bss空间进行0初始化

.为使虚拟内存工作,要保证最少的管理信息.然后是虚拟空间动作.

也就是,调用cpu有强的依赖关系的过程init386()(@i386/i386/machdep.c),

然后进行kernel内的管理信息初始化,i/o设备的登记,生成4个kernel process

,再调用main()(@kern/init_main.c).当main()返回locore.s时,应该有如下

5个进程:

PID TT STAT TIME COMMAND

0 ?? DLs 0:00.17 (swapper)

1 ?? Is 0:00.19 /sbin/init --

2 ?? DL 0:56.60 (pagedaemon)

3 ?? DL 0:00.06 (vmdaemon)

4 ?? DL 6:07.65 (updata)

从locore.s返回到process #1,/sbin/init开始动作,然后转向freebsd的普通

动作.

init386()和main()的处理大致如下:

.init386()

GDT和LDT,IDT,task stages处理的初始化,例外处理等locore.s没做的

事情,虚拟内存初始化.然后,根据boot program的参数,增加物理内

存page数.然后,作成process #0的雏形.

.main()

逐步调用构成kernel模块的的初始化部分.

但是,kernel构成的各个模块的初始化子程序一个个的列举出来运行很显然是

不行的.通常是利用时间连表的技能来运行它(ld command).也就是,程序

是以很多个source分开编译和联结.相同的模块名字就对应于相同的地址来进

行调用.它在时间链表里面自动调节执行.

初始化时候,main()函数要call的模块利用在sys/kernel.h里面定义的宏

SYSINIT()和SYSINIT_KT()进行登记.这样,kernel在link的时候,ld命令就

能够得到那些信息和进行配置列表.这个列表就是kernel的组成模块的初始化

routine的登记.检查source,

就可以找到初始化routine的部分.

如表:

print_caddr_t(copyright) kern/init_main.c

vm_men_init(NULL) vm/vm_init.c

syctl_order(&sysctl_) kern/kern_sysctl.c

kmemnit(NULL) kern/kern_malloc.c

fpu_init(NULL) i386/i386/math_emulate.c

cpu_startup(NULL) i386/i386/machdep.c

gnufpu_init(NULL) miscfs/devfs/devfs_tree.c

...

各个device的major号与处理routine的登记 (major循序号)

...

configure(NULL) i386/i386/autoconf.c

proc0_init(NULL) kern/init_main.c

rqinit(NULL) kern/kern_synch.c

vm_init_limits(&proc0) vm/vm_glue.c

vfsinit(NULL) kern/vfs_init.c

elf_insert_brand_entry(&linux_brand) i386/linux/linux_sysvec.c

initclocks(NULL) kern/kern_clock.c

mbinit(NULL) kern/uipc_mbuf.c

clst_init(NULL) kern/tty_subr.c

shmnit(NULL) kern/sysv_shm.c

seminit(NULL) kern/sysv_sem.c

msginit(NULL) kern/sysc_msg.c

kludge_splimp(&x_save_spl) kern/uipc_domain.c

ifinit(NULL) net/if.c

domaininit(NULL) kern/uipc_domain.c

kludge_splx(&x_save_spl) kern/uipc_domain.c

kmstartup(NULL) kern/subr_prof.c

sched_setup(NULL) kern/init_main.c

xxx_vfs_mountroot(NULL) kern/init_main.c

xxx_vfs_root_fdtab(NULL) kern/init_main.c

swapinit(NULL) kern/init_main.c

proc0_post(NULL) kern/init_main.c

kthread_init(NULL) kern/init_main.c||

kproc_start(&page_kp) vm/vm_pageout.c||

kproc_start(&vm_kp) vm/vm_pageout.c||

kproc_start(&up_kp) kern/vfs_bio.c||

scheduler(NULL) vm/vm_glue.c

(||表示有多个程序)

proc-post()被呼叫后,main()就是在对应process 0 的kernel的虚拟

内存里动作.kthread_init(),kproc_start(&page_kp),kproc_start(&vm_kp)

,kproc_start(&up_kp)等这几个进程,在fork()后相继被调用.它就是相

应的进程1,2,3,4等.

除process 1 以外,其他的进程调用并不返回调用的地址.(也就是,main()

的跟随执行后,并不返回locore.s).对于process #1的kernel的虚拟内存,

在kthread_init()返回后,main()的跟随就完了,回到locore.s后,process #1

的进程空间的配置文件/sbin/init就被执行.

main()在process #0对应的kernel虚拟内存运行后,进入时间链表scheduler().

这个并不返回.那现在就有五个进程了.

然后,fork() 的调用在下面说明.

1,分配process ID,保证struct proc()用的空间.

2,复制父亲的process的虚拟内存空间,作成物理内存的变换表.对

应两个进程,采用相对应的物理内存表.

3,给回父亲的struct proc和struct user,然后对子进程的struct和

struct user进行初始化.

4,kernel的stacker也进行复制.

5,返回父进程后,标记生成的子进程.完成处理.

但是,process #0 -- 4 这五个进程的虚拟内存里面什么都没有.这些是核心

进程的特殊部分.进程0,2,3是调节系统存在的进程的执行优先级,监视物理

内存的不足,如果不够就使用swap区进行交换.进程4的作用就是定期调查核心

的unix文件系统的管理信息与驱动程序的管理信息的一致性,使它的信息一直

是最新的.



1.3.3 /sbin/init

从kernel里面看,/sbin/init就是单一的进程空间里动作,与一般的

user program一样,提供user使用的unix文件系统的环境的服务.

核心启动后最初的动作就是/sbin/init.作用如下:

.确保file system的一致性,进行mount.

.之后,network的设定和各种daemon的启动.

.监视终端的login的配置和动作状态.这个动作完了后(logout),

修改和配置 login.

也就是说,如果没有它,用户就不能使用unix文件系统.还有就是,如果boot

progam参数指定-s的话,它就过渡到单一的用户模式.相对来说,普通的用户

模式也就是multi模式.为了使普通用户能够使用系统,/sbin/init的参考文件

主要在/etc目录里放着.主要就是运行/etc/rc文件对系统进行初始化.

/etc/rc文件的主要内容和作用如下:

.使系统能够使用swap区

.检查/etc/fstab,检查它的连贯性,如果有问题就转到单一的用户模式

.mount nfs以外的文件系统

.读入network 的设定和各种daemon进程的设定情况的记录文件

/etc/c.conf,这个内容作为shell script的变量设定,以下的就是

各个shell的动作调整

.serial的初始化(/etc/rc.serial)

.运行PCMCIA卡的插拔监控守护进程(/etc/rc.pccard)

.network的部分初始化(/etc/rc.network)

.如果有nfs的时候就进行mount操作

.network的最终初始化(/etc/rc.network:启动和entwork有关的daemon)

.共有库的有关信息的初始化

.intd,lpd,sendmail的启动

.依赖系统的一些初始化进程

/etc/rc的处理完了后,/sbin/init就对/etc/ttys等记述的一些终端的用户login进行

监视.对于这个,/etc/ttys里指定的终端,fork()后的进程里:

.exec()指定的程序(普通的情况是/usr/libexec/getty)

./usr/libexec/getty进行终端速度等的设定.提示login:,等待用户输入

.用户输入后,名字作为参数exec() /etc/bin/login

./usr/bin/login就提示出passwd:,等待用户的输入

.准备user名和passwd,对输入的用户名进行确定,正确的话就exec()用户

shell



下图就是/sbin/init的监视进程图:



process #1

-------------------------------------------------------->

/sbin/init | ^ \

| fork() | | fork()

+ exec() exec() exec() | | exec()

process #n |---------->+--------->+------------------*+--------

getty login user的login shell process #m



2.2 虚拟文件系统和v-node

FreeBSD在disk上的除了ffs以外还可以操作各种各样的文件系统.主要的如

下:

.cd9660

可以对ISO9660形式的cd-rom的目录/文件构造的文件系统进行mount,

locate等目录层的操作

.ms-dos

对ms-dos文件系统进行目录层次的mount,定位等操作

.mfs

通过使用虚拟内存对swap区的一部分进行unix文件系统的构造,定位

作为目录的一部分进行读写

.nfs

由nfs server提供的remote目录级进行mount,定位的目录层操作.

.null

对已经存在的目录层的使用别名

.union

对已有的目录A(上层)在下层目录B上进行重叠 (不大理解这的意思

,大概是在下层目录里面又嫁接了上层目录的意思:译者).文件名的查

找由上层优先进行.没有的话就转道下层.如果对下层的文件进行写操作

,它的拷贝就在上层上进行.举例说明,作业目录在上层,但cd-rom的源

在下层,两个目录重叠,那么编译source的时候,就相当方便了.

.procfs

对于进程号的目录作成mount point.通过文件名对各个目录的进程进行

控制.

.kernfs

为了对动作中的kernel有关的信息进行参考,而作成的mount point

.fdesc

对于各个进程,用它所打开的文件柄对应的文件作成的mount point



实际上,在核心内部,为了对它们进行统一操作,就对文件系统和v-node

进行抽象化,实际的处理过程就是调用各类的文件系统的模块进行处理.



2.2.1对虚拟文件系统的操作

各个文件系统可以提供的操作的一览如下,它在struct vfsops

(@sys/mount.h)里面定义:

.对文件系统进行mount的操作

.本文件系统的开始动作的操作

.本文件系统的umount操作

.表达文件系统的根的v-node的查找操作

.对一般用户的权限控制

.取得文件系统的状态

.内存内的管理信息写入介质中

.从i-node到v-node的取得操作

.v-node和nfs的文件柄的相互变换的操作

.文件系统实际的模块的初始化



对于文件系统,各个实际的操作routine在vfsops的形式提供准备工作.各个文件系

统的vfsops,在以下的表里的source进行定义:



--------------------------------------------------------------

file system vfsops的定义 source

--------------------------------------------------------------

ufs ufs_vfsops ufs/ffs/ffs_vfsops.c

cd9660 cd9660_vfsops isofs/cd9660/cd9660_vfsops.c

msdos msdosfs_vfsops msdosfs/msdosfs_vfsops.c

mfs mfs_vfsops ufs/mfs/mfs_vfops.c

nfs nfs_vfsops nfs/nfs_vfsops.c

null null_vfsops miscfs/nullfs/null_vfsops.c

nuion union_vfsops miscfs/union/union_vfsops.c

procfs procfs_vfsops miscfs/procfs/procfs_vfsops.c

kernfs kernfs_vfsops miscfs/kernfs/kernfs_vfsops.c

fdesc fdesc_vfsops miscfs/fdesc/fdesc_vfsops.c

devfs devfs_vfsops miscfs/devfs/devfs_vfsops.c

ext2fs ext2fs_vfsops gnu/ext2fs/ext2_vfsops.c

lfs lfs_vfsops ufs/lfs/lfs_vfsops.c

portal portal_vfsops miscfs/portal.portal_vfsops.c

umap umap_vfsops miscfs/umapfs/umap_vfsops.c

---------------------------------------------------------------

这些就是文件系统的实际模块(*_vfsops.c),文件系统名称,文件系统号等等

在struct vfsconf(@sys/mount.h)里面汇总,各个模块里用宏VFS_SET()进入核

心.

根据main()(@kern/init_main.c),在kernel初始化的过程中,vfsinit()

(@kern/vfs_init.c)里面有

struct vfsconf *vfsconf[MOUNT_MAXTYPE+1];

struct vfsops *vfssw[MOUNT_MAXTYPE+1];

各种东西的设定,这些是,管理mount信息的struct mount(@sys/mount.h)的成员

mnt_vfc和mnt_op要指定所对应的文件系统的vfsconf,vfssw.还有宏VFS_操作名

(struct mount *,..)里,可以各个操作的调用.



2.2.2对v-node的操作

虚拟文件系统就是通过对i-node的抽象化之后的v-node的文件/目录进行io处理.

为了这个目的,作为对v-node的适用处理,有

.从v-node到文件名的查找,返回v-node

.打开/关闭v-node

.检查是否可能访问v-node

.得到-v-node的属性

.设定v-node的属性

.对v-node的输入/输出

.扩展v-node的硬连接和符号连接

.对v-node进行目录的作成和删除

....

由这里开始,一共定义了41个.

v-node由struct vnode(@sys/vnode.h)里定义,作为类别在enum vtype

里面表示出来,一共是9种类.它包含着在各个文件系统上对各个的文件/目录(包

括特殊)文件进行统一识别的信息.为了实现这样,v-node一连串的操作就是在各

模块里通过宏VNODEOP_SET()和核心通讯.这些操作名和实现的routine只需要必要

的几个对应.在核心初始化里,vfs_opv_init()(@kern/vfs_init.c)就使从数据得

到的号码一一对应,收集了routine的地址的同一size的配列再进行组合.各个

v-node就一个一个指向这些配列.对v-node的操作在vnode_if.h里定义:

它以

VOP_操作名(v-node,...)

的统一形式记述.



下面是对v-node的操作的定义source:

------------------------------------------------------------------------

各个v-node操作(vnodeopv) source

------------------------------------------------------------------------

cd9660_fifoop_opv_desc isofs/cd9660/cd9660_vnops.c

cd9660_specop_opv_desc isofs/cd9660/cd9660_vnops.c

cd9660_vnodeop_opv_desc isofs/cd9660/cd9660_vnops.c

dead_vnodop_opv_desc miscfs/deadfs/dead_devfs_vnops.c

devfs_vnodeop_desc miscfs/devfs/devfs_vnops.c

ext2fs_fifoop_opv_desc gnu/ext2fs/ext2fs_vnops.c

ext2fs_specop_opv_desc gnu/ext2fs/ext2fs_vnops.c

ext2fs_vnodeop_opv_desc gnu/ext2fs/ext2fs_vnops.c

fdesc_vnodeop_opv_desc miscfs/fdesc/fdesc_vnops.c

ffs_fifoop_opv_desc ufs/ffs/ffs_vnops.c

ffs_specop_opv_desc ufs/ffs/ffs_vnops.c

ffs_vnodeop_opv_desc ufs/ffs/ffs_vnops.c

fifo_nfsv2nodeop_opv_desc nfs/nfs_vnops.c

fifo_vnodeop_opv_desc miscfs/fifofs/fifo_vnops.c

kernfs_vnodeop_opv_desc miscfs/kernfs/kernfs_vnops.c

lfs_fifoop_opv_desc ufs/lfs/lfs_vnops.c

lfs_specop_opv_desc ufs/lfs/lfs_vnops.c

lfs_vnodeop_opv_desc ufs/lfs/lfs_vnops.c

mfs_vnodeop_opv_desc ufs/mfs/mfs_vnops.c

msdosfs_vnodeop_opv_desc msdosfs/msdosfs_vnops.c

nfsv2_vnodeop_opv_desc nfs/nfs_vnops.c

null_vnodeop_opv_desc miscfs/nullfs/null_vnops.c

portal_vnodeop_opv_desc miscfs/portal/portal_vnops.c

procfs_vnodeop_opv_desc miscfs/procfs/procfs_vnops.c

spec_nfsv2nodeop_opv_desc nfs/nfs_vnops.c

spec_vnodeop_opv_desc miscfs/specfs/spec_vnops.c

umap_vnodeop_opv_desc miscfs/umapfs/umap_vnops.c

union_vnodeop_opv_desc miscfs/union/union_vnops.c

------------------------------------------------------------------------

这个基础上,spec_vnodeop_opv_spec里描述的操作群就是device driver

interface的东西!!



2.2.3mount根目录之前的处理概要

mount根目录的时候,main()(@kern/init_main.c)的初始化的过程从xxx_vfs_mountroot()

(@kern/init_mail.c)被调用开始.如果处理过程正常,就对rootvp设定包含了root的

v-node.

.main()的初始化过程中,configure()(@autoconf.c)被调用.在这个,io设备

初始化完了后,就转移到如下两个变量的地址:一个是mountroot,是处理mount的routine,

另一个是mountrootvfsops,是处理虚拟文件系统的routine.在本机磁盘中,就进入变量

rootdev所指定的disk号中.这里就是,假定本机磁盘

mountroot vfs_mountroot

mountrootvfsop &ufs_vfsops

rootdev boot disk number



.xxx_vfs_mountroot()(@kern/init_main.c)

运行(*mountroot)(mountrootvfsops)后,就指明了root file system的mount.

.vfs_mountroot()(@kern/vfs_conf.c)

管理mount的了文件系统的信息的struct mount(@sys/mount.h),对它进行确认

,然后设定传递过来的对虚拟文件系统的操作群(&ufs_vfsops),才进行"root"

标记.根据VFS_MOUNT(mp,...)进行mount这个虚拟文件系统.mount成功后,就

追加file system的list.这里,由于传递了&ufs_vfsops,就可以调用

ffs_mount()(@ufs/ffs/ffs_vfsops.c)

.ffs_mount()

首先调用bdevvp()(@kern/vfs_subr.c),进行VBLK类别,spec_vnodeop_p

(@misc/specfs/spec_vnops.c) v-node操作,保证设定了驱动号的rootdev的

v-node的最新信息,然后设定rootvp.最后,通过ffs_mountfs()调用进行实际

的mount rootvp操作.

.ffs_mountfs()

各种各样的检查完了后,调用VOP_OPEN(),打开rootvp的v-node.在这里,如果

v-node的v_op成员在spec_vnodeop_p存在的话,就调用spec_open()(@misc/

specfs/spec_vnops.c).

.spec_open

由于VBLK里包含v-node的种类,从v-node指定的device号取得major的号,

调用对应driver的XXopen() routine



续上,由VOP_IOCTL()(还是的通过spec_ioctl()(@misc/specfs/spec_vnops.c))

可以得到partition信息,然后该检查super block的内容.正确的话,就在struct

ufsmount(@ufs/ufs/ufsmount.h)设定unix file system,这样处理过程就完了.



2.2.4 struct buf 和block的输入输出routine

前节的ffs_mountfs()提到使用bread()(@kern/vfs_bio.c)读出partition的

super block.这个接口函数很快就会解释.它主要用于读取block型的device到

kernel内部的buffer中.

bread(struct vnode *vp, /*(in)输入对象的v-node*/

daddr_t blkno, /*(in)block号*/

int size, /*(in)读出的byte数量,block长的倍数*/

struct ucred * cred,/*(in)权限信息*/

struct buf ** bpp)/*(out)存储读来的data*/

同样的buffer link后的block输出的子程序是bwrite().

bwrite(struct buf *bp) /*(out)可以输出的struct buf*/

两者之间共同的地方就是struct buf(@/sys/buf.h),它用于io处理中给device driver

做桥梁作用的数据结构.它记录了v-node,io的区别,可以io的block位置/byte数,存

储实际data buffer的address,io处理的进展情况等.



bread则通过getblk()对block输入的结构struct buf进行操作.getblk()调用在核心

管理buffer link和返回指定大小的block的struct buf.这个(缓冲区)内容在目的

block是否存在与指定v-node的指定位置block是否已经构成缓冲环有关.struct buf

里面有一个标志位,当缓冲环内容变化是,这个标志位就会改变.bread()根据这个

flag判断block是否已经缓冲,如果已经完成,它就终止退出.如果不是这样,则在

struct buf的mark里面标志,然后调用VOP_STRATEGY().在v-node登记的strategy

routine记录了io处理的过程,所以bread()当实际的处理完了后,就调用biowait()

进入等待状态.然后,就转移到别的进程A.io处理完了后,调用biodone(),进程A

也可以继续进行.还有,调用bread()的一边,当完成操作后,就调用brelse(),在

里面对struct buf的flag重新设置,让它对别的程序开放.



bwrite也是同样的通过VOP_STRATEGY()对io处理要求进行登记,同时也调用biowait()

进入等待状态,同样,当实际操作完了后,也设置flag进行复位,使得其他程序可以

使用io,当空闲的时候,io就挂起,转向其他进程处理.

进程等待进入的时候,当然不限于只是调用biowait().在bread()或者bwrite()之前,

系统必须分配足够的资源供它使用,比如一些缓冲区等.当进行实际io时候,1个block

也可以,多个block也可以,而且这样可以获得更高的效率,这样看起来,就象实际上

是连续操作了.

2.2.5系统调用open()的处理概要

进程通过系统调用read()/write()进行io处理,它由文件描述符指定对哪里进

行i/o,文件描述符是0以上的整数,它在各个进程的struct proc的成员

struct filedesc *p_fd(struct filedesc(@sys/filedesc.h))保留的struct file

((@sys/file.h)进行选择添加.对struct file,它含有从文件的头的输入输出的byte

位置,输入操作,输出操作,输入输出控制,输入输出的准备状态的检查,执行close

的routine,以及描述io处理对象的信息(v-node,socket,pipe) .系统调用open()

(@kern/vfs_syscalls.c)就是把包含路径信息的v-node找寻出来,为了对它进行io处理,

先要对struct file进行初始化,然后返回文件描述符.

从路径名查找v-nodehe和io准备操作由vn_open()(@kern/vfs_vnops.c)承担.

vn_open()通过namei()(@kern/vfs_lookup.c)查找路径对应的v-node名,由VOP_OPEN()

调用不同的v-node定义的准备过程routine.例如,有如下的处理方法.

.普通的file/directory

调用ufs_open()(@ufs/ufs/ufs_vnops.c),检查open的mode

.特殊设备文件

调用spec_open()(@miscfs/specfs/spec_vnops.c)

文字型 调用device driver的open routine

快型 mount的时候出错.如果不是这样,就调用device driver的

open routine.



回过头来,namei()的任务是就是,对于指定的路径名,对应于跟目录或者当

前目录的v-node作为起点,通过lookup()(@kern/vfs_lookup.c)进行v-node查找.

lookup()从路径名开始的v-node(VDIR)开始查找.找到了的v-node作为新的起点继续进行

查找下一步的要素名,然后得到目的的v-node.这个时候,根据v-node的不同,目录的检

索方法也就不同.各个要素的实际检索由VOP_LOOKUP()来做.



2.2.6系统调用read()的处理概要

open()取得文件描述符后,对它的输入处理,有如下的流程.指定的文件描述符

的struct file内登记的处理routine有vn_read()(@kern/vfs_vnops.c),vn_write(),

vn_ioctl(),vn_select(),vn_closefile(),v_node

登记的操作routine不能分开使用.vn_*()里,只有在合适的前缀操作下,才能正确调用.

read()首先在struct uio(@sys/uio.h)登记进程指定的buffer的位置和长度.

执行read()后,vn_read()向struct file设定登记的文件的读写位置,然后调用VOP_READ().

根据读出来的byte数,读写位置相应增加.

VOP_READ()的call routine则是与v-node有关,就象下图一样.



vn_read()

文字型/块型 |

/------------------

| | file/directory

spec_read() ---------ffs_read()-------VOP_READ()

block型 | |

/---------------|char型 |

bread() device driver bread()

| |

spec_strategy() ---------------ufs_strategy() --VOP_STRATEGY()

| | |

| | |

device driver spec_strategy() -------------/

|

|

device driver





.普通的file/directory

调用ffs_read()(@ufs/ufs/ufs_readwrite.c).对应指定的读写位置,计算block

的位置,然后用bread()读出来.读出来的数据送到进程所准备的缓冲区.从bread()

传递过来的block并不是物理block的位置,而是把file作为block列的一个理论值.

从理论块到物理块的变换由VOP_STARATEGY()完成.也就是说,ufs_strategy()先把

文件内位置转化为物理block位置,然后从v-node记录的i-node把表示物理设备的

v-node 去出来,这个VOP_STRATEGY就调用spec()(@miscfs/specfs/spec_vnops.c)

让它进行输入要求.

.特殊设备文件

通过调用spec_read()(@miscfs/specfs/spec_vnops.c),把它分为文字型和块型两类.

文字型 调用device driver的输入routine

块型 通过bread()进行输入处理



对文件的系统调用write()的场合也是类似的处理流程(ufs_write()->bwrite()),

ufs_write()则要考虑到文件大小的延伸.

2.3Device Driver

进程的io要求到这里说的差不多了.上面也解说了对于文字型,块型的驱动程序接口,就

是dev_spec_vnodeop_opv_desc里定义的子函数那些.参考设备驱动程序,在sys/conf.h

里定义的结构体.block型是

struct bdevsw{

d_open_t *d_open;

d_close_t *d_close;

d_strategy_t *d_strategy;

d_ioctl_t *d_ioctl;

d_dump_t *d_dump;

d_psize_t *d_psize; /*得到容量*/

int *d_flags;

char *d_name; /*device 名*/

struct cdesw *d_cdev; /*对应的文字型*/

int d_maj; /*major号*/

}

文字型的则是

struct cdevsw{

d_open_t *d_open;

d_close_t *d_close;

d_read_t *d_read; /* rawread() */

d_write_t *d_write; /* rawwrite()*/

d_ioctl_t *d_ioctl;

d_stop_t *d_stop; /* nostop()*/

d_reset_t *d_reset; /* nullreset()*/

d_devtotty_t *d_devtotty; /* nodevtotty*/

d_select_t *d_select; /* deltrue*/

d_mmap_t *d_mmap; /* nommap*/

d_strategy_t *d_strategy

char *d_name; /*device名*/

struct bdevsw *d_bdev; /*对应block型*/

int d_may; /*major号*/

}





两方面共同的部分有

xx_open(dev_t dev,int oflags,int devtype,struct proc *p)

xx_close(dev_t dev,int fflag,int devtype,struct proc *p)

xx_ioctl(dev_t dev,int cmd,caddr_t data,int fflag,struct proc *p)

xx_open()用于打开device号的设备.xx_close()则用于关闭它.xx_ioctl()则对设备的

动作状态,机能的取得,设置等进行控制,它通过int cmd命令和参数caddr_t data对之

进行处理.xx_open()的oflags则是系统调用open()里指定的标志.xx_close()和

xx_ioctl()的fflag是每个文件描述符设定的标志.int devtype用来区别设备类型是文

字型的还是块型的.struct proc *p是本次要求的进程号.



在文字型的操作里,有这三个函数

xx_read(dev_t dev,struct uio *uio,int ioflag)

xx_write(dev_t dev,struct uio *uio,int ioflag)

xx_select(dev_t dev,int which, struct proc *p)

xx_read()/xx_write()是对device号的io,struct uio *uio 是io的buffer,int ioflag

标志io动作的option.例如,输入data没准备好的场合不用进入等待状态也可以.

xx_select()检查是否可以进行io要求.

在块设备的操作中,有一个函数

xx_strategy(struct buf *bp)

它处理io要求.struct buf *bp里面包含着device号,输入还是输出,io的buffer等.



device号中的major号,对文字型的struct cdevsw *cdevsw[],对块型的struct

bdevsw *bdevsw[],作为配列的添加字使用.向这些配列登记,就可以调出device driver

的登记routine.

对cdevsw[]登记的过程在kern/kern_conf.c,它使用

int cdevsw_add(

dev_t *descrip, /*收集device号的变量的指针*/

struct cdevsw *newentry,/*设置struct cdevsw的指针*/

struct cdevsw **oldentry,/*旧的设定内容的返回领域*/

)

另一方面,对bdevsw[]的登记过程则使用

int bdevsw_add_generic(

int bdev, /*block型的major号*/

int cdev, /*文字型的major浩*/

struct bdevsw *bdevsw, /*设定struct bdevsw的指针,对应d_cdev*/

)

block型的device和char型的device有着一定的对应关系.这些结构体相互参考.

bdevsw_add_generic()从block的结构体开始,对作为char型的device的结构体进行初始化.

还有,network interface的devive driver,并没有向cdevsw[]和bdevsw[]登记.而且也没有

device号.网络间的package流,和进程间与网络间的package流也没有特别指明.

调用登记routine的时候,可以把文件系统的modules作为特殊设备文件参考.登记

routine在什么地方都可以调用.

.main()(@kern/init_mail.c)的初始化过程中登记的routine调用的时候,各个

device driver的modules里由宏SYSINIT()准备进行.

.确认device driver里的io设备的存在的时候,调用登记routine.

当调用登记程序段的时候,如果major号和/dev/MAKEDEV的major号有冲突的时候,

就调用全部无关性device file的处理routine,也可能没有预期的的灾难事情.还有别的

以外事情,就是当/dev里没有对应的特殊设备文件的时候,也就不能从进程进行参考.

2.3.1驱动程序初始化

从文件系统的模块可以看出来,如果要对驱动程序的物理设备进行io,必须

先对它们进行初始化,否则不能处理process的io.核心初始化的过程里,一共登记

了两个基本的操作过程.

1.probe 确认io设备

2.attach 设置device driver内部的数据结构,使它能够对io设备

进行操作.登记中断子程序.

在device driver中的处理过程有:

1.i/o地址

i/o命令使用的地址,使io设备的控制硬件和数据交换.

2.中断号

io设备的状态变化的时候,向cpu发出通知.

3.共有内存地址

根据设备的不同,使用一部分内存空间进行cpu和数据的交换.

4.DMA通道

不用通过cpu做中介,设备和内存直接交换数据时候采用的通道的识别号.

cpu可以在数据传送的时候同时执行它的机器语言.

前两种是必须有的.设备根据他连接的总线设备不一样,处理过程也就不同.

这个在核心的configure中反映出来.



各种总线设备的device driver的初始化

驱动程序的初始化在main()初始化的过程中调用configure()

(@i386/i386autoconf.c).



EISA bus

连接EISA bus的io设备用的device driver的初始化在eisa_configure()

(@i386/eisa/eisaconf.c).各个device driver在module里对struct eisa_driver

XXX(@i386/eisa/eisaconf.h)进行probe,attach等的设置,准备在宏DATA_SET

(eisadriver_set,XXX)进行登记.

eisa_configure()(@i386/eisa/eisaconf.c)对连接EISA bus的全部io设备

标志和i/o地址进行检测.之后便调用登记的probe子程序.在probe子程序中,通过

eisa_match_dev()(@i386/eisa/eisaconf.c)对自身检测,查找io设备,检测i/o中断

号,然后进行使用预定,之后用eisa_registerdev()(@i386/eisa/eisaconf.c)在

struct eisa_driver XXX对这个设备操作,作为device driver登记.全部的io设备

的控制device driver登记完毕后,eisa_configure()就调用device driver的attach

子程序.attach子程序则进行中断处理程序的登记和device driver的数据的初始化.



核心的configure文件登记了以下的一些device driver:

--------------------------------------------------------------------------

device device driver的情报 source 参考

--------------------------------------------------------------------------

mainboard_drv i386/eisa/eisaconf.c

ahb ahb_eisa_driver i386/eisa/aha1742.c scsi adapt

ahc ahc_eisa_driver i386/eisa/aic7770.c scsi adapt

bt bt_eisa_driver i386/eisa/bt74x.c scsi adapt

ep ep_eisa_driver i386/eisa/3c5x9.c network interface

fea pdq_eisa_driver i386/eisa/if_fea.c network interface

vx vx_eisa_driver i386/eida/if_vx_eisa.c network interface

--------------------------------------------------------------------------



PCI bus

连接pci bus的设备的初始化在pci_configure()(@pci/pci.c)进行.各个

device driver在module内的struct pci_device XXX(@pci/pcivar.h)设置probe和

attach,在通过宏DATA_SET(pcidevice_est,XXX)进行登记.

DATA_SET(pcibus_set,i386pci)(@i386/isa/pcibus.c)登记的子程序可以

得到有关pci bus的一些信息.之后和eisa bus处理过程一样进行各种各样的调用.

核心的configure文件登记了以下的一些device driver:

--------------------------------------------------------------------------

device device driver的情报 source 参考

--------------------------------------------------------------------------

ahc ahc_pci_driver pci/aic7870.c scsi adapt

bt bt_pci_driver pci/bt9xx.c scsi adapt

ncr ncr_device pci/ncr.c scsi adapt

amd trmamd_device pci/tek390.c scsi adapt

cy cy_device pci/cy_pci.c serial port

meteor met_device pci/meteor.c meteor通道

stl stlpcidriver i386/isa/stallion.c serial port

wdc wdc_pci_driver pci/wdc_p.c ide control

de dedevice pci/if_de.c network interface

ed ed_pci_driver pci/if_ed_p.c network interface

fpa pfadevice pci/if_pfa.c network interface

fxp fxp_device pci/if_pxp.c network interface

lnc lnc_pci_driver pci/if_lnc_p.c network interface

sr sr_pci_driver pci/if_sr_p.c network interface

vx vxdevice pci/if_vx_pci.c network interface

-------------------------------------------------------------------------



ISA bus

连接ISA bus的io设备的device driver的初始化在isa_configure()(@i386/

isa/isa.c)进行.和EISA,PCI很大的一个区别就是,在核心的配置文件中,要指定所

有的io地址等.

configure文件中,有象如下的记录

controller 控制设备名 at isa?...

device device名 at isa?...

这些内容在编译核心的目录下作为ioconf.c的struct isa_device

isa_devtab_XXX[]的初始值由config命令写进去.在struct isa_device(@i386/isa

/isa_device.h)的上,其次的成员变量由configure文件的记录内容进行设定.但是

,“名字”是控制设备名/device名的数字除外的部分.

-------------------------------------------------------------------------

member名 configure的记述内容

-------------------------------------------------------------------------

id_driver 名字drvier

id_iobase prot I/O address

id_irq irq号

id_drq drq DMA通道号

id_maddr iomem共有memory address

id_msize iosiz共有memory长度

id_intr vector device driver的中断处理程序名

id_unit 名字的后的数字(?)

id_flags flags

-------------------------------------------------------------------------



但是,和控制设备/device名有关的一些东西如bio,net,tty出现的场合,这

些一般成为isa_devtab_bio[],isa_devtab_net[],isa_devtab_tty[]数组的初始值.

没有的情况,则成为isa_tab_null[]的初始值.还有一个就是名字driver,它是各个

device driver的module内部的struct isa_driver(@i386/isa/isa_device.h)一个东

西.对isa bus设备的device driver,这个是一个固定值.

象这样的记录:

------------------------------------------------------------

disk device名 at 控制设备名 driver 数字

tape device名 at 控制设备名 driver 数字

------------------------------------------------------------

每个数字除外控制设备名(wdc或者fdc),总结起来就是写进一个叫做

isa_biotab_控制设备名[]的数组的某个元素的初始设定值.但对unit成员填入数字

外,其他的也就和isa_devtab_bio[]的内容一样.

isa_configure()依照isa_devtab_bio[],isa_devtab_net[],

isa_devtab_tty[]的设定值调用probe子程序对设备的有无进行确认.有的话就继续

调用attach子程序.

probe子程序对设备进行确认,不同的probe子程序也有可能对同样的io地址

进行操作.所以为了防止这个问题,isa_configure()对已经确认过的的io地址不再

给别的probe进行动作.

同样,错认的可能性也有的.必要的时候没连接的设备的probe要禁止使用,

(在boot的参数的时候).



2.4进程通信

(socket和network interface)



网络协议和路由选择的过程中,为选择合适的网络接口,一般进程没必要对

网络接口进行明确指示.所以,网络卡就不和文件系统的名字对应,而是在核心内部

的struct ifnet(@net/if.h)进行对应.用命令ifconfig和netstat -i就可以看到各个

网卡的struct ifnet的参数.

unix中,进程通信和网络介入的通信的场合,使用network interface.进行

通信的进程,根据socket()的系统调用,把socket作为通信手段.这个时候,需要指

定协议族,通信类型,具体的协议等.综合起来,有如下的参数:



int socket( int domain, //protocal family

int type, //通信形式

int protocal //具体的协议

)



根据protocal family可以推算出可以通信的范围,同时,可以控制通信类型和具体的

协议.通信类型有如下的四个种类.



.SOCK_STREAM

基于连接的传输,具有可靠性,可以保证通信的发送接收顺序的双向通信路.

但不限制数据的传输顺序的紧急数据发送情况也有可能.发送数据和接受数据的

的间隔不一定.比如一次发送10个byte,可能一次接受完毕,但发送100个byte,

可能一次接受完毕,也可能分两次接收.



.SOCK_DREAM

可靠性比较低.不是基于连接传输.每次的发送数据长度是有限制的.



.SOCK_SEQPACKET

基于连接的传输,具有可靠性,可以保证通信的发送接收顺序的双向通信路.

和.SOCK_STREAM不同的地方是,发送数据和接受数据的间隔一定,每次的发送长度

也是限制的.



.SOCK_RAW

核心内部的network protocal和interface的控制中使用.



通信类型受制于具体的协议.freebsd协议族有如下的一些:

---------------------------------------------------------------------------

协议族 通信类型 定义模块

PF_ROUTE SOCK_RAW net/rtsock.c

PF_APPLETALK SOCK_DGRAM netatalk/at_proto.c

PF_INET SOCK_DGRAM netinet/in_proto.c

SOCK_STREAM

SOCK_RAW

PF_IPX SOCK_DRAM netipx/ipx_proto.c

SOCK_STREAM

SOCK_SEQACKET

SOCK_RAW

PF_KEY SOCK_RAW netkey/key.c

PF_NS SOCK_DGRAM netns/ns_proto.c

SOCK_STREAM

SOCK_SEQACKET

SOCK_RAW

PF_LOCAL SOCK_STREAM kern/uipc_proto.c

SOCK_DGRAM

--------------------------------------------------------------------------



这些协议族由各个struct domain里定义.在这里面,当前的协议族里可能利用的通信

类型可以在struct protosw里找到.protocal family的登记可以把宏DOMAIN_SET()设

定的信息通过函数domaininit()(@kern/uipc_domain.c)读到kernel的初始化中.



由系统调用socket()取得的socket,在进程里可以象文件柄一样的进行操作.

适当的设置后,可以使用read()/write()调用.由open()取得的文件柄对应read()/

write()/ioctl()/select()/close()处理可以使用vn_read()/vn_write()/vn_ioctl()

/vn_select()/vn_close()进行相应替换,对socket的处理则是soo_read()/soo_write

()/soo)ioctl()/soo_select()/soo_close()(@kern/sys_socket.c)进行取代.对应于

socket的struct protosw的子过程则在任何地方都可以进行处理.还有可以用send()

/sendto()/sendmsg()进行发送,recv()/resvfrom()/recvmsg()进行接收.



socket是绑定在通信路上的,所以,如果不规定通信对象的端子,就不能进行

实际的通信操作.也就是说,必须指定对象和自己的socket的地址.这个和protocal

family不大一样,它由struct sockaddr_XXX指定.但是对socket指定地址的系统调用

对每个socket family并没有统一性,系统调用层次的地址在struct sockaddr(@sys/

socket.h)里面包含.这样,就可以区别它的成员sa_family里各个

struct sockaddr_XXX 了.

s = socket(PF_INET,SOCK_DGRAM,0)取得的socket由band()进行address的设

定后,

sendto(s,msg,mlen,sockaddr_to,tolen)

里的 message msg向sockaddr_to的地址送去,kernel内则顺序进行如下的操

作处理.



1.系统调用sendto()的处理过程sendto()(@kern/uipc_syscalls.c)就是,得

到参数后在调用sendit()(@kern/uipc_syscalls.c).

2.sendit()先从参数得到发送的地址,再调用sosend()

(@kern/uipc_socket.c).

3.sosend()从struct uio取得发送数据,然后向struct mbuf变换.建立socket

时候所指定address family/protocal的处理过程把

so_proto->pr_usrreqs->pru_send调出来.在这里调用了udp_userreq()(@netinet/

udp_usrreq.c).

4.udp_usrreq()是PRU_SEND处理,它调用了udp_output().

5.udp_output是UDP包的分组变换,它调用ip_output()(@netinet/ip_output).

6.ip_output()的功能是从发送ip地址开始,选择network interface的子过程.

如果是ethernet,则调用ether_output()(@net/if_ethersubr.c).还有IP firewall

的处理也在这里进行.

7.ether_output()则由发送ip地址取得ethernet的地址.进行ethernet打包,

调用在network interface里设定的子过程if_start.再对network card进行io.
返回页首
阅览会员资料 发送站内信件 发送电子邮件 MSN Messenger
从以前的文章开始显示:   
发表新文章   这个论题已经被锁定,您不能发表、回复或者编辑文章。    FreeBSD China -> 中文-桌面-开发-调试 论坛时间为 北京时间
1页/共1

 
转跳到:  
不能发布新主题
不能在这个论坛回复主题
不能在这个论坛编辑自己的文章
不能在这个论坛删除自己的文章
不能在这个论坛发表投票


Powered by phpBB 2023cc © 2003 Opensource Steps; © 2003-2009 The FreeBSD Simplified Chinese Project
Powered by phpBB © 2001, 2005 phpBB Group
Protected by Project Honey Pot and phpBB.cc
silvery-trainer
The FreeBSD China Project 网站: 中文计划网站 社区网站
The FreeBSD China Project 版权所有 (C) 1999 - 2003 网页设计版权 著作权和商标