joyfire linux筆記 感謝原做者

 

內核分析

 

目 錄

  1. index.html
  2. 更新記錄
  3. 發佈
    1. 申明
    2. GPL
    3. GFDL
  4. 系統管理
  5. 程序編寫
  6. 內核分析
    1. 啓動
      1. 啓動步驟
      2. setup.s
      3. head.s
      4. bootsect.s
      5. compressed/misc.c
      6. setup.txt
      7. bootsect.txt
      8. 用網卡從並口啓動(I386)
      9. 內核解壓
    2. 中斷
      1. 軟中斷
      2. 驅動中斷
      3. 硬件中斷 from aka
      4. 定時器代碼分析
      5. from lisolog
        1. index
        2. 內部中斷
        3. 外部中斷
        4. 後續處理
    3. 內存
      1. 用戶態
      2. 內核頁目錄的初始化
      3. 內核線程頁目錄的借用
      4. 用戶進程內核頁目錄的創建
      5. 內核頁目錄的同步
      6. 內存管理 from aka
      7. mlock代碼分析
      8. memory.c
      9. mmap.c
      10. 夥伴(buddy)算法
      11. 頁目錄處理的宏
    4. 進程
      1. 信號
      2. sched.c
      3. 進程信號隊列
      4. SMP
      5. 內核線程頁目錄的借用
      6. 代碼分析
      7. 線程
      8. 進程描述符
      9. init進程從內核態切換到用戶態
      10. SET_LINKS
      11. REMOVE_LINKS
      12. get_wchan()
      13. sigframe的結構
      14. rt_sigframe結構
      15. 信號隊列的結構
    5. 系統調用
      1. 系統調用的實現
      2. 添加系統調用
      3. 系統調用簡述
      4. 增長系統調用
      5. mlock代碼分析
    6. 文件系統
      1. inode
      2. proc
      3. CD_ROM
      4. 文件頁緩衝結構
      5. 塊設備緩衝區結構
      6. 散列算法
      7. permission(inode,mask)
      8. IDE硬盤驅動器讀寫
    7. 驅動
      1. PCI
      2. loopback.c
      3. 網卡驅動編寫
      4. 設備驅動(from hust)
      5. 設備驅動(from smth)
      6. 數據接口卡
      7. 代碼
        1. 標準範例
          1. header.c
          2. init.c
          3. ioctl.c
          4. open.c
          5. read.c
          6. release.c
          7. tdd.h
          8. write.c
        2. Sis 900
      8. 網上站點
    8. 經驗
      1. AKA推薦書籍
      2. linux論壇推薦資源
      3. 數據結構
      4. 從新編譯
      5. 重建內核選項
      6. 調試技術
      7. ptrace進程跟蹤
      8. 宏#與##


內核分析

 

[目錄]


啓動

 

[目錄]


啓動步驟


系統引導: css

涉及的文件
./arch/$ARCH/boot/bootsect.s
./arch/$ARCH/boot/setup.s html

bootsect.S
 這個程序是linux kernel的第一個程序,包括了linux本身的bootstrap程序,
可是在說明這個程序前,必須先說明通常IBM PC開機時的動做(此處的開機是指
"打開PC的電源"): 前端

  通常PC在電源一開時,是由內存中地址FFFF:0000開始執行(這個地址必定
在ROM BIOS中,ROM BIOS通常是在FEOOOh到FFFFFh中),而此處的內容則是一個
jump指令,jump到另外一個位於ROM BIOS中的位置,開始執行一系列的動做,包
括了檢查RAM,keyboard,顯示器,軟硬磁盤等等,這些動做是由系統測試代碼
(system test code)來執行的,隨着製做BIOS廠商的不一樣而會有些許差別,但都
是大同小異,讀者可自行觀察自家機器開機時,螢幕上所顯示的檢查訊息。 node

  緊接着系統測試碼以後,控制權會轉移給ROM中的啓動程序
(ROM bootstrap routine),這個程序會將磁盤上的第零軌第零扇區讀入
內存中(這就是通常所謂的boot sector,若是你曾接觸過電腦病
毒,就大概聽過它的大名),至於被讀到內存的哪裏呢? --絕對
位置07C0:0000(即07C00h處),這是IBM系列PC的特性。而位在linux開機
磁盤的boot sector上的正是linux的bootsect程序,也就是說,bootsect是
第一個被讀入內存中並執行的程序。如今,咱們能夠開始來
看看到底bootsect作了什麼。 linux

第一步
 首先,bootsect將它"本身"從被ROM BIOS載入的絕對地址0x7C00處搬到
0x90000處,而後利用一個jmpi(jump indirectly)的指令,跳到新位置的
jmpi的下一行去執行, ios

第二步
 接着,將其餘segment registers包括DS,ES,SS都指向0x9000這個位置,
與CS看齊。另外將SP及DX指向一任意位移地址( offset ),這個地址等一下
會用來存放磁盤參數表(disk para- meter table ) git

第三步
 接着利用BIOS中斷服務int 13h的第0號功能,重置磁盤控制器,使得剛纔
的設定發揮功能。 程序員

第四步
 完成重置磁盤控制器以後,bootsect就從磁盤上讀入緊鄰着bootsect的setup
程序,也就是setup.S,此讀入動做是利用BIOS中斷服務int 13h的第2號功能。
setup的image將會讀入至程序所指定的內存絕對地址0x90200處,也就是在內存
中緊鄰着bootsect 所在的位置。待setup的image讀入內存後,利用BIOS中斷服
務int 13h的第8號功能讀取目前磁盤的參數。 redis

第五步
 再來,就要讀入真正linux的kernel了,也就是你能夠在linux的根目錄下看
到的"vmlinuz" 。在讀入前,將會先呼叫BIOS中斷服務int 10h 的第3號功能,
讀取遊標位置,以後再呼叫BIOS 中斷服務int 10h的第13h號功能,在螢幕上輸
出字串"Loading",這個字串在boot linux時都會首先被看到,相信你們應該覺
得很眼熟吧。 算法

第六步
 接下來作的事是檢查root device,以後就仿照一開始的方法,利用indirect
jump 跳至剛剛已讀入的setup部份

第七步
  setup.S完成在實模式下版本檢查,並將硬盤,鼠標,內存參數寫入到 INITSEG
中,並負責進入保護模式。

第八步
  操做系統的初始化。

[目錄]


setup.s


setup.S
A summary of the setup.S code 。The slight differences in the operation of setup.S due to a big kernel is documented here. When the switch to 32 bit protected mode begins the code32_start address is defined as 0x100000 (when loaded) here.
code32_start:

#ifndef __BIG_KERNEL__
.long 0x1000
#else
.long 0x100000
#endif

After setting the keyboard repeat rate to a maximum, calling video.S, storing the video parameters, checking for the hard disks, PS/2 mouse, and APM BIOS the preparation for real mode switch begins.

The interrupts are disabled. Since the loader changed the code32_start address, the code32 varable is updated. This would be used for the jmpi instruction when the setup.S finally jumps to compressed/head.S. In case of a big kernel this is loacted at 0x100000.

seg cs
mov eax, code32_start !modified above by the loader
seg cs
mov code32,eax

!code32 contains the correct address to branch to after setup.S finishes After the above code there is a slight difference in the ways the big and small kernels are dealt. In case of a small kernel the kernel is moved down to segment address 0x100, but a big kernel is not moved. Before decompression, the big kernel stays at 0x100000. The following is the code that does thischeck.test byte ptr loadflags,

#LOADED_HIGH
jz do_move0 ! a normal low loaded zImage is moved
jmp end_move ! skip move

The interrupt and global descriptors are initialized:

lidt idt_48 ! load idt wit 0,0
lgdt gdt_48 ! load gdt with whatever appropriate

After enabling A20 and reprogramming the interrupts, it is ready to set the PE bit:

mov ax,#1
lmsw ax
jmp flush_instr
flush_instr:
xor bx.bx !flag to indicate a boot
! Manual, mixing of 16-bit and 32 bit code
db 0x166,0xea !prefix jmpi-opcode
code32: dd ox1000 !this has been reset in caes of a big kernel, to 0x100000
dw __KERNEL_CS

Finally it prepares the opcode for jumping to compressed/head.S which in the big kernel is at 0x100000. The compressed kernel would start at 0x1000 in case of a small kernel.

compressed/head.S

When setup.S relinquishes control to compressed/head.S at beginning of the compressed kernmel at 0x100000. It checks to see if A20 is really enabled otherwise it loops forever.

Itinitializes eflags, and clears BSS (Block Start by Symbol) creating reserved space for uninitialized static or global variables. Finally it reserves place for the moveparams structure (defined in misc.c) and pushes the current stack pointer on the stack and calls the C function decompress_kernel which takes a struct moveparams * as an argument

subl $16,%esp
pushl %esp
call SYMBOL_NAME(decompress_kernel)
orl ??,??
jnz 3f

Te C function decompress_kernel returns the variable high_loaded which is set to 1 in the function setup_output_buffer_if_we_run_high, which is called in decompressed_kernel if a big kernel was loaded.
When decompressed_kernel returns, it jumps to 3f which moves the move routine.

movl $move_routine_start,%esi ! puts the offset of the start of the source in the source index register
mov $0x1000,?? ! the destination index now contains 0x1000, thus after move, the move routine starts at 0x1000
movl $move_routine_end,??
sub %esi,?? ! ecx register now contains the number of bytes to be moved
! (number of bytes between the labels move_routine_start and move_routine_end)
cld
rep
movsb ! moves the bytes from ds:si to es:di, in each loop it increments si and di, and decrements cx
! the movs instruction moves till ecx is zero

Thus the movsb instruction moves the bytes of the move routine between the labels move_routine_start and move_routine_end. At the end the entire move routine labeled move_routine_start is at 0x1000. The movsb instruction moves bytes from ds:si to es:si.

At the start of the head.S code es,ds,fs,gs were all intialized to __KERNEL_DS, which is defined in /usr/src/linux/include/asm/segment.h as 0x18. This is the offset from the goobal descriptor table gdtwhich was setup in setup.S. The 24th byte is the start of the data segment descriptor, which has the base address = 0. Thus the moe routine is moved and
starts at offset 0x1000 from __KERNEL_DS, the kernel data segment base (which is 0).
The salient features of what is done by the decompress_kernel is discussed in the next section but it is worth noting that the when the decompressed_kernel function is invoked, space was created at the top of the stack to contain the information about the decompressed kernel. The decompressed kernel if big may be in the high buffer and in the low buffer. After the decompressed_kernel function returns, the decompressed kernel has to be moved so that we
have a contiguous decompressed kernel starting from address 0x100000. To move the decompressed kernel, the important parameters needed are the start addresses of the high buffer and low buffer, and the number of bytes in the high and low buffers. This is at the top of the stack when decompressed_kernel returns (the top of the stack was passed as an argument : struct moveparams*, and in the function the fileds of the moveparams struture was adjusted toreflect the state of the decompression.)

/* in compressed/misc.c */
struct moveparams {
uch *low_buffer_start; ! start address of the low buffer
int count; ! number of bytes in the low buffer after decompression is doneuch *high_buffer_start; ! start address of the high buffer
int hcount; ! number of bytes in the high buffer aftre decompression is done
};

Thus when the decompressed_kernel returns, the relevant bytes are popped in the respective registers as shown below. After preparing these registers the decompressed kernel is ready to be moved and the control jumps to the moved move routine at __KERNEL_CS:0x1000. The code for setting the appropriate registers is given below:

popl %esi ! discard the address, has the return value (high_load) most probably
popl %esi ! low_buffer_start
popl ?? ! lcount
popl ?? ! high_buffer_count
popl ?? ! hcount
movl %0x100000,??
cli ! disable interrutps when the decompressed kernel is being moved
ljmp $(__KERNEL_CS), $0x1000 ! jump to the move routine which was moved to low memory, 0x1000

The move_routine_start basically has two parts, first it moves the part of the decompressed kernel in the low buffer, then it moves (if required) the high buffer contents. It should be noted that the ecx has been intialized to the number of bytes in the low end buffer, and the destination index register di has been intialized to 0x100000.
move_routine_start:

rep ! repeat, it stops repeating when ecx == 0
movsb ! the movsb instruction repeats till ecx is 0. In each loop byte is transferred from ds:esi to es:edi! In each loop the edi and the esi are incremented and ecx is decremented
! when the low end buffer has been moved the value of di is not changed and the next pasrt of the code! uses it to transfer the bytes from the high buffer
movl ??,%esi ! esi now has the offset corresponding to the start of the high  buffer
movl ??,?? ! ecx is now intialized to the number of bytes in the high buffer
rep
movsb ! moves all the bytes in the high buffer, and doesn’t move at all if hcount was zero (if it was determined, in! close_output_buffer_if_we_run_high that the high buffer need not be moveddown )
xorl ??,??
mov $0x90000, %esp ! stack pointer is adjusted, most probably to be used by the kernel in the intialization
ljmp $(__KERNEL_CS), $0x100000 ! jump to __KERNEL_CS:0X100000, where the kernel code starts
move_routine_end:At the end of the this the control goes to the kernel code segment.


Linux Assembly code taken from  head.S and setup.S
Comment code added by us

[目錄]


head.s


由於setup.S最後的爲一條轉跳指令,跳到內核第一條指令並開始執行。
指令中指向的是內存中的絕對地址,咱們沒法依此判斷轉跳到了head.S。

可是咱們能夠經過Makefile簡單的肯定head.S位於內核的前端。

在arch/i386 的 Makefile 中定義了
HEAD := arch/i386/kernel/head.o

而在linux總的Makefile中由這樣的語句
include arch/$(ARCH)/Makefile
說明HEAD定義在該文件中有效

而後由以下語句:
vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs
$(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o /
  $(ARCHIVES) /
  $(FILESYSTEMS) /
  $(DRIVERS) /
  $(LIBS) -o vmlinux
$(NM) vmlinux | grep -v '/(compiled/)/|/(/.o$$/)/|/( a /)' | sort > System.map

從這個依賴關係咱們能夠得到大量的信息

1>$(HEAD)即head.o的確第一個被鏈接到核心中

2>全部內核中支持的文件系統所有編譯到$(FILESYSTEMS)即fs/filesystems.a中
  全部內核中支持的網絡協議所有編譯到net.a中
  全部內核中支持的SCSI驅動所有編譯到scsi.a中
  ...................
  原來內核也不過是一堆庫文件和目標文件的集合罷了,有興趣對內核減肥的同窗,
  能夠好比如較一下看到底是那個部分佔用了空間。

3>System.map中包含了全部的內核輸出的函數,咱們在編寫內核模塊的時候
  能夠調用的系統函數大概就這些了。


好了,消除了心中的疑問,咱們能夠仔細分析head.s了。

Head.S分析

1 首先將ds,es,fs,gs指向系統數據段KERNEL_DS
  KERNEL_DS 在asm/segment.h中定義,表示全局描述符表中
  中的第三項。
  注意:該此時生效的全局描述符表並非在head.s中定義的
        而仍然是在setup.S中定義的。

2 數據段所有清空。

3 setup_idt爲一段子程序,將中斷向量表所有指向ignore_int函數
  該函數打印出:unknown interrupt
  固然這樣的中斷處理函數什麼也幹不了。

4 察看數據線A20是否有效,不然循環等待。
  地址線A20是x86的歷史遺留問題,決定是否能訪問1M以上內存。

5 拷貝啓動參數到0x5000頁的前半頁,而將setup.s取出的bios參數
  放到後半頁。

6 檢查CPU類型
  @#$#%$^*@^?(^%#$%!#!@?誰知道幹了什麼?

7 初始化頁表,只初始化最初幾頁。

  1>將swapper_pg_dir(0x2000)和pg0(0x3000)清空
    swapper_pg_dir做爲整個系統的頁目錄

  2>將pg0做爲第一個頁表,將其地址賦到swapper_pg_dir的第一個32
    位字中。

  3>同時將該頁表項也賦給swapper_pg_dir的第3072個入口,表示虛擬地址
    0xc0000000也指向pg0。

  4>將pg0這個頁表填滿指向內存前4M

  5>進入分頁方式
    注意:之前雖然在在保護模式但沒有啓用分頁。

    --------------------
    |  swapper_pg_dir  |       -----------
    |                  |-------| pg0     |----------內存前4M
    |                  |       -----------
    |                  |
    --------------------
8 裝入新的gdt和ldt表。

9 刷新段寄存器ds,es,fs,gs

10 使用系統堆棧,即預留的0x6000頁面

11 執行start_kernel函數,這個函數是第一個C編制的
   函數,內核又有了一個新的開始。

[目錄]


bootsect.s


發發信人: seis (矛), 信區: Linux
標  題: Linux操做系統內核引導程序詳細剖析
發信站: BBS 水木清華站 (Fri Feb  2 14:12:43 2001)

! bootsect.s (c) 1991, 1992 Linus Torvalds 版權全部
! Drew Eckhardt修改過
! Bruce Evans (bde)修改過
!
! bootsect.s 被bios-啓動子程序加載至0x7c00 (31k)處,並將本身
! 移到了地址0x90000 (576k)處,並跳轉至那裏。
!
! bde - 不能盲目地跳轉,有些系統可能只有512k的低
! 內存。使用中斷0x12來得到(系統的)最高內存、等。
!
! 它而後使用BIOS中斷將setup直接加載到本身的後面(0x90200)(576.5k),
! 並將系統加載到地址0x10000處。
!
! 注意! 目前的內核系統最大長度限制爲(8*65536-4096)(508k)字節長,即便是在
! 未來這也是沒有問題的。我想讓它保持簡單明瞭。這樣508k的最大內核長度應該
! 是足夠了,尤爲是這裏沒有象minix中同樣包含緩衝區高速緩衝(並且尤爲是如今
! 內核是壓縮的 :-)
!
! 加載程序已經作的儘可能地簡單了,因此持續的讀出錯將致使死循環。只能手工重啓。
! 只要可能,經過一次取得整個磁道,加載過程能夠作的很快的。

#include /* 爲取得CONFIG_ROOT_RDONLY參數 */
!! config.h中(即autoconf.h中)沒有CONFIG_ROOT_RDONLY定義!!!?

#include

.text

SETUPSECS = 4 ! 默認的setup程序扇區數(setup-sectors)的默認值;

BOOTSEG = 0x7C0 ! bootsect的原始地址;

INITSEG = DEF_INITSEG ! 將bootsect程序移到這個段處(0x9000) - 避開;
SETUPSEG = DEF_SETUPSEG ! 設置程序(setup)從這裏開始(0x9020);
SYSSEG = DEF_SYSSEG ! 系統加載至0x1000(65536)(64k)段處;
SYSSIZE = DEF_SYSSIZE ! 系統的大小(0x7F00): 要加載的16字節爲一節的數;
!! 以上4個DEF_參數定義在boot.h中:
!! DEF_INITSEG 0x9000
!! DEF_SYSSEG 0x1000
!! DEF_SETUPSEG 0x9020
!! DEF_SYSSIZE 0x7F00 (=32512=31.75k)*16=508k

! ROOT_DEV & SWAP_DEV 如今是由"build"中編制的;
ROOT_DEV = 0
SWAP_DEV = 0
#ifndef SVGA_MODE
#define SVGA_MODE ASK_VGA
#endif
#ifndef RAMDISK
#define RAMDISK 0
#endif
#ifndef CONFIG_ROOT_RDONLY
#define CONFIG_ROOT_RDONLY 1
#endif

! ld86 須要一個入口標識符,這和一般的同樣;
.globl _main
_main:
#if 0 /* 調試程序的異常分支,除非BIOS古怪(好比老的HP機)不然是無害的 */
int 3
#endif
mov ax,#BOOTSEG !! 將ds段寄存器置爲0x7C0;
mov ds,ax
mov ax,#INITSEG !! 將es段寄存器置爲0x9000;
mov es,ax
mov cx,#256 !! 將cx計數器置爲256(要移動256個字, 512字節);
sub si,si !! 源地址 ds:si=0x07C0:0x0000;
sub di,di !! 目的地址es:di=0x9000:0x0000;
cld !! 清方向標誌;
rep !! 將這段程序從0x7C0:0(31k)移至0x9000:0(576k)處;
movsw !! 共256個字(512字節)(0x200長);
jmpi go,INITSEG !! 間接跳轉至移動後的本程序go處;

! ax和es如今已經含有INITSEG的值(0x9000);

go: mov di,#0x4000-12 ! 0x4000(16k)是>=bootsect + setup 的長度 +
! + 堆棧的長度 的任意的值;
! 12 是磁盤參數塊的大小 es:di=0x94000-12=592k-12;

! bde - 將0xff00改爲了0x4000以從0x6400處使用調試程序(bde)。若是
! 咱們檢測過最高內存的話就不用擔憂這事了,還有,個人BIOS能夠被配置爲將wini驅動

! 放在內存高端而不是放在向量表中。老式的堆棧區可能會搞亂驅動表;

mov ds,ax ! 置ds數據段爲0x9000;
mov ss,ax ! 置堆棧段爲0x9000;
mov sp,di ! 置堆棧指針INITSEG:0x4000-12處;
/*
* 許多BIOS的默認磁盤參數表將不能
* 進行扇區數大於在表中指定
* 的最大扇區數( - 在某些狀況下
* 這意味着是7個扇區)後面的多扇區的讀操做。
*
* 因爲單個扇區的讀操做是很慢的並且固然是沒問題的,
* 咱們必須在RAM中(爲第一個磁盤)建立新的參數表。
* 咱們將把最大扇區數設置爲36 - 咱們在一個ED 2.88驅動器上所能
* 遇到的最大值。
*
* 此值過高是沒有任何害處的,可是低的話就會有問題了。
*
* 段寄存器是這樣的: ds=es=ss=cs - INITSEG,(=0X9000)
* fs = 0, gs沒有用到。
*/

! 上面執行重複操做(rep)之後,cx爲0;

mov fs,cx !! 置fs段寄存器=0;
mov bx,#0x78 ! fs:bx是磁盤參數表的地址;
push ds
seg fs
lds si,(bx) ! ds:si是源地址;
!! 將fs:bx地址所指的指針值放入ds:si中;
mov cl,#6 ! 拷貝12個字節到0x9000:0x4000-12開始處;
cld
push di !! 指針0x9000:0x4000-12處;

rep
movsw

pop di !! di仍指向0x9000:0x4000-12處(參數表開始處);
pop si !! ds => si=INITSEG(=0X9000);

movb 4(di),*36 ! 修正扇區計數值;

seg fs
mov (bx),di !! 修改fs:bx(0000:0x0078)處磁盤參數表的地址爲0x9000:0x4000-12;
seg fs
mov 2(bx),es

! 將setup程序所在的扇區(setup-sectors)直接加載到boot塊的後面。!! 0x90200開始處
;
! 注意,es已經設置好了。
! 一樣通過rep循環後cx爲0

load_setup:
xor ah,ah ! 復位軟驅(FDC);
xor dl,dl
int 0x13

xor dx,dx ! 驅動器0, 磁頭0;
mov cl,#0x02 ! 從扇區2開始,磁道0;
mov bx,#0x0200 ! 置數據緩衝區地址=es:bx=0x9000:0x200;
! 在INITSEG段中,即0x90200處;
mov ah,#0x02 ! 要調用功能號2(讀操做);
mov al,setup_sects ! 要讀入的扇區數SETUPSECS=4;
! (假釋全部數據都在磁頭0、磁道0);
int 0x13 ! 讀操做;
jnc ok_load_setup ! ok則繼續;

push ax ! 不然顯示出錯信息。保存ah的值(功能號2);
call print_nl !! 打印換行;
mov bp,sp !! bp將做爲調用print_hex的參數;
call print_hex !! 打印bp所指的數據;
pop ax

jmp load_setup !! 重試!

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!INT 13 - DISK - READ SECTOR(S) INTO MEMORY
!! AH = 02h
!! AL = number of sectors to read (must be nonzero)
!! CH = low eight bits of cylinder number
!! CL = sector number 1-63 (bits 0-5)
!! high two bits of cylinder (bits 6-7, hard disk only)
!! DH = head number
!! DL = drive number (bit 7 set for hard disk)
!! ES:BX -> data buffer
!! Return: CF set on error
!! if AH = 11h (corrected ECC error), AL = burst length
!! CF clear if successful
!! AH = status (see #00234)
!! AL = number of sectors transferred (only valid if CF set for some
!! BIOSes)
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


ok_load_setup:

! 取得磁盤驅動器參數,特別是每磁道扇區數(nr of sectors/track);

#if 0

! bde - Phoenix BIOS手冊中提到功能0x08只對硬盤起做用。
! 但它對於個人一個BIOS(1987 Award)不起做用。
! 不檢查錯誤碼是致命的錯誤。

xor dl,dl
mov ah,#0x08 ! AH=8用於取得驅動器參數;
int 0x13
xor ch,ch

!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! INT 13 - DISK - GET DRIVE PARAMETERS (PC,XT286,CONV,PS,ESDI,SCSI)
!! AH = 08h
!! DL = drive (bit 7 set for hard disk)
!!Return: CF set on error
!! AH = status (07h) (see #00234)
!! CF clear if successful
!! AH = 00h
!! AL = 00h on at least some BIOSes
!! BL = drive type (AT/PS2 floppies only) (see #00242)
!! CH = low eight bits of maximum cylinder number
!! CL = maximum sector number (bits 5-0)
!! high two bits of maximum cylinder number (bits 7-6)
!! DH = maximum head number
!! DL = number of drives
!! ES:DI -> drive parameter table (floppies only)
!!!!!!!!!!!!!!!!!!!!!!!!!!!!

#else

! 好象沒有BIOS調用可取得扇區數。若是扇區36能夠讀就推測是36個扇區,
! 若是扇區18可讀就推測是18個扇區,若是扇區15可讀就推測是15個扇區,
! 不然推測是9. [36, 18, 15, 9]

mov si,#disksizes ! ds:si->要測試扇區數大小的表;

probe_loop:
lodsb !! ds:si所指的字節 =>al, si=si+1;
cbw ! 擴展爲字(word);
mov sectors, ax ! 第一個值是36,最後一個是9;
cmp si,#disksizes+4
jae got_sectors ! 若是全部測試都失敗了,就試9;
xchg ax,cx ! cx = 磁道和扇區(第一次是36=0x0024);
xor dx,dx ! 驅動器0,磁頭0;
xor bl,bl !! 設置緩衝區es:bx = 0x9000:0x0a00(578.5k);
mov bh,setup_sects !! setup_sects = 4 (共2k);
inc bh
shl bh,#1 ! setup後面的地址(es=cs);
mov ax,#0x0201 ! 功能2(讀),1個扇區;
int 0x13
jc probe_loop ! 若是不對,就試用下一個值;

#endif

got_sectors:

! 恢復es

mov ax,#INITSEG
mov es,ax ! es = 0x9000;

! 打印一些無用的信息(換行後,顯示Loading)

mov ah,#0x03 ! 讀光標位置;
xor bh,bh
int 0x10

mov cx,#9
mov bx,#0x0007 ! 頁0,屬性7 (normal);
mov bp,#msg1
mov ax,#0x1301 ! 寫字符串,移動光標;
int 0x10

! ok, 咱們已經顯示出了信息,如今
! 咱們要加載系統了(到0x10000處)(64k處)

mov ax,#SYSSEG
mov es,ax ! es=0x01000的段;
call read_it !! 讀system,es爲輸入參數;
call kill_motor !! 關閉驅動器馬達;
call print_nl !! 打印回車換行;

! 這之後,咱們來檢查要使用哪一個根設備(root-device)。若是已指定了設備(!=0)
! 則不作任何事而使用給定的設備。不然的話,使用/dev/fd0H2880 (2,32)或/dev/PS0
(2,28)
! 或者是/dev/at0 (2,8)之一,這取決於咱們假設咱們知道的扇區數而定。
!! |__ ps0?? (x,y)--表示主、次設備號?

seg cs
mov ax,root_dev
or ax,ax
jne root_defined
seg cs
mov bx,sectors !! sectors = 每磁道扇區數;
mov ax,#0x0208 ! /dev/ps0 - 1.2Mb;
cmp bx,#15
je root_defined
mov al,#0x1c ! /dev/PS0 - 1.44Mb !! 0x1C = 28;
cmp bx,#18
je root_defined
mov al,0x20 ! /dev/fd0H2880 - 2.88Mb;
cmp bx,#36
je root_defined
mov al,#0 ! /dev/fd0 - autodetect;
root_defined:
seg cs
mov root_dev,ax !! 其中保存由設備的主、次設備號;

! 這之後(全部程序都加載了),咱們就跳轉至
! 被直接加載到boot塊後面的setup程序去:

jmpi 0,SETUPSEG !! 跳轉到0x9020:0000(setup程序的開始位置);


! 這段程序將系統(system)加載到0x10000(64k)處,
! 注意不要跨越64kb邊界。咱們試圖以最快的速度
! 來加載,只要可能就整個磁道一塊兒讀入。
!
! 輸入(in): es - 開始地址段(一般是0x1000)
!
sread: .word 0 ! 當前磁道已讀的扇區數;
head: .word 0 ! 當前磁頭;
track: .word 0 ! 當前磁道;

read_it:
mov al,setup_sects
inc al
mov sread,al !! 當前sread=5;
mov ax,es !! es=0x1000;
test ax,#0x0fff !! (ax AND 0x0fff, if ax=0x1000 then zero-flag=1 );
die: jne die ! es 必須在64kB的邊界;
xor bx,bx ! bx 是段內的開始地址;
rp_read:
#ifdef __BIG_KERNEL__
#define CALL_HIGHLOAD_KLUDGE .word 0x1eff, 0x220 ! 調用 far * bootsect_kludge
! 注意: as86不能彙編這;
CALL_HIGHLOAD_KLUDGE ! 這是在setup.S中的程序;
#else
mov ax,es
sub ax,#SYSSEG ! 當前es段值減system加載時的啓始段值(0x1000);
#endif
cmp ax,syssize ! 咱們是否已經都加載了?(ax=0x7f00 ?);
jbe ok1_read !! if ax <= syssize then 繼續讀;
ret !! 全都加載完了,返回!
ok1_read:
mov ax,sectors !! sectors=每磁道扇區數;
sub ax,sread !! 減去當前磁道已讀扇區數,al=當前磁道未讀的扇區數(ah=0);
mov cx,ax
shl cx,#9 !! 乘512,cx = 當前磁道未讀的字節數;
add cx,bx !! 加上段內偏移值,es:bx爲當前讀入的數據緩衝區地址;
jnc ok2_read !! 若是沒有超過64K則繼續讀;
je ok2_read !! 若是正好64K也繼續讀;
xor ax,ax
sub ax,bx
shr ax,#9
ok2_read:
call read_track !! es:bx ->緩衝區,al=要讀的扇區數,也即當前磁道未讀的扇區數;

mov cx,ax !! ax仍爲調用read_track以前的值,即爲讀入的扇區數;
add ax,sread !! ax = 當前磁道已讀的扇區數;
cmp ax,sectors !! 已經讀完當前磁道上的扇區了嗎?
jne ok3_read !! 沒有,則跳轉;
mov ax,#1
sub ax,head !! 當前是磁頭1嗎?
jne ok4_read !! 不是(是磁頭0)則跳轉(此時ax=1);
inc track !! 當前是磁頭1,則讀下一磁道(當前磁道加1);
ok4_read:
mov head,ax !! 保存當前磁頭號;
xor ax,ax !! 本磁道已讀扇區數清零;
ok3_read:
mov sread,ax !! 存本磁道已讀扇區數;
shl cx,#9 !! 剛纔一次讀操做讀入的扇區數 * 512;
add bx,cx !! 調整數據緩衝區的起始指針;
jnc rp_read !! 若是該指針沒有超過64K的段內最大偏移量,則跳轉繼續讀操做;
mov ax,es !! 若是超過了,則將段地址加0x1000(下一個64K段);
add ah,#0x10
mov es,ax
xor bx,bx !! 緩衝區地址段內偏移量置零;
jmp rp_read !! 繼續讀操做;

 

read_track:
pusha !! 將寄存器ax,cx,dx,bx,sp,bp,si,di壓入堆棧;
pusha
mov ax,#0xe2e ! loading... message 2e = . !! 顯示一個.
mov bx,#7
int 0x10
popa

mov dx,track !! track = 當前磁道;
mov cx,sread
inc cx !! cl = 扇區號,要讀的起始扇區;
mov ch,dl !! ch = 磁道號的低8位;
mov dx,head !!
mov dh,dl !! dh = 當前磁頭號;
and dx,#0x0100 !! dl = 驅動器號(0);
mov ah,#2 !! 功能2(讀),es:bx指向讀數據緩衝區;

push dx ! 爲出錯轉儲保存寄存器的值到堆棧上;
push cx
push bx
push ax

int 0x13
jc bad_rt !! 若是出錯,則跳轉;
add sp, #8 !! 清(放棄)堆棧上剛推入的4個寄存器值;
popa
ret

bad_rt: push ax ! 保存出錯碼;
call print_all ! ah = error, al = read;


xor ah,ah
xor dl,dl
int 0x13


add sp,#10
popa
jmp read_track

/*
* print_all是用於調試的。
* 它將打印出全部寄存器的值。所做的假設是
* 從一個子程序中調用的,並有以下所示的堆棧幀結構
* dx
* cx
* bx
* ax
* error
* ret <- sp
*
*/

print_all:
mov cx,#5 ! 出錯碼 + 4個寄存器
mov bp,sp

print_loop:
push cx ! 保存剩餘的計數值
call print_nl ! 爲了加強閱讀性,打印換行

cmp cl, #5
jae no_reg ! 看看是否須要寄存器的名稱

mov ax,#0xe05 + A - l
sub al,cl
int 0x10

mov al,#X
int 0x10

mov al,#:
int 0x10

no_reg:
add bp,#2 ! 下一個寄存器
call print_hex ! 打印值
pop cx
loop print_loop
ret

print_nl: !! 打印回車換行。
mov ax,#0xe0d ! CR
int 0x10
mov al,#0xa ! LF
int 0x10
ret

/*
* print_hex是用於調試目的的,打印出
* ss:bp所指向的十六進制數。
* !! 例如,十六進制數是0x4321時,則al分別等於4,3,2,1調用中斷打印出來 4321
*/

print_hex:
mov cx, #4 ! 4個十六進制數字
mov dx, (bp) ! 將(bp)所指的值放入dx中
print_digit:
rol dx, #4 ! 循環以使低4比特用上 !! 取dx的高4比特移到低4比特處。
mov ax, #0xe0f ! ah = 請求的功能值,al = 半字節(4個比特)掩碼。
and al, dl !! 取dl的低4比特值。
add al, #0x90 ! 將al轉換爲ASCII十六進制碼(4個指令)
daa !! 十進制調整
adc al, #0x40 !! (adc dest, src ==> dest := dest + src + c )
daa
int 0x10
loop print_digit
ret


/*
* 這個過程(子程序)關閉軟驅的馬達,這樣
* 咱們進入內核後它的狀態就是已知的,之後也就
* 不用擔憂它了。
*/
kill_motor:
push dx
mov dx,#0x3f2
xor al,al
outb
pop dx
ret

!! 數據區
sectors:
.word 0 !! 當前每磁道扇區數。(36||18||15||9)

disksizes: !! 每磁道扇區數表
.byte 36, 18, 15, 9

msg1:
.byte 13, 10
.ascii "Loading"

.org 497 !! 從boot程序的二進制文件的497字節開始
setup_sects:
.byte SETUPSECS
root_flags:
.word CONFIG_ROOT_RDONLY
syssize:
.word SYSSIZE
swap_dev:
.word SWAP_DEV
ram_size:
.word RAMDISK
vid_mode:
.word SVGA_MODE
root_dev:
.word ROOT_DEV
boot_flag: !! 分區啓動標誌
.word 0xAA55

[目錄]


compressed/misc.c


compressed/misc.c
The differences in decompressing big and small kernels.
http://www.vuse.vanderbilt.edu/~knopfdg/documentation/hw3_part3.htm
The function decompressed_kernel is invoked from head.S and a parameter to the top of the stack is passed to store the results of the decompression namely, the start addresses of the high and the low buffers which contain the decompressed kernel and the numebr of bytes in each buffer (hcount and lcount).

int decompress_kernel(struct moveparams *mv)
{
if (SCREEN_INFO.orig_video_mode == 7) {
vidmem = (char *) 0xb0000;
vidport = 0x3b4;
} else {
vidmem = (char *) 0xb8000;
vidport = 0x3d4;
}
lines = SCREEN_INFO.orig_video_lines;
cols = SCREEN_INFO.orig_video_cols;
if (free_mem_ptr < 0x100000) setup_normal_output_buffer(); // Call if smallkernel
else setup_output_buffer_if_we_run_high(mv); // Call if big kernel
makecrc();
puts("Uncompressing Linux... ");
gunzip();
puts("Ok, booting the kernel./n");
if (high_loaded) close_output_buffer_if_we_run_high(mv);
return high_loaded;
}

The first place where a distinction is made is when the buffers are to be setup for the decmpression routine gunzip(). Free_mem_ptr, is loaded with the value of the address of the extern variabe end. The variable end marks the end of the compressed kernel. If the free_mem-ptr is less than the 0x100000,then a high buffer has to be setup. Thus the function setup_output_buffer_if_we_run_high is called and the pointer to the top of the moveparams structure is passed so that when the buffers are setup, the start addresses fields are updated in moveparams structure. It is also checked to see if the high buffer needs to be moved down after decompression and this is reflected by the hcount which is 0 if we need not move the high buffer down.

void setup_output_buffer_if_we_run_high(struct moveparams *mv)
{
high_buffer_start = (uch *)(((ulg)&end) HEAP_SIZE);
//the high buffer start address is at the end HEAP_SIZE
#ifdef STANDARD_MEMORY_BIOS_CALL
if (EXT_MEM_K < (3*1024)) error("Less than 4MB of memory./n");
#else
if ((ALT_MEM_K > EXT_MEM_K ? ALT_MEM_K : EXT_MEM_K) < (3*1024)) error("Less
than 4MB of memory./n");
#endif
mv->low_buffer_start = output_data = (char *)LOW_BUFFER_START;
//the low buffer start address is at 0x2000 and it extends till 0x90000.
high_loaded = 1; //high_loaded is set to 1, this is returned by decompressed_kernel
free_mem_end_ptr = (long)high_buffer_start;
// free_mem_end_ptr points to the same address as te high_buffer_start
// the code below finds out if the high buffer needs to be moved after decompression
// if the size if the low buffer is > the size of the compressed kernel and the HEAP_SIZE
// then the high_buffer_start has to be shifted up so that when the decompression starts it doesn’t
// overwrite the compressed kernel data. Thus when the high_buffer_start islow then it is shifted
// up to exactly match the end of the compressed kernel and the HEAP_SIZE. The hcount filed is
// is set to 0 as the high buffer need not be moved down. Otherwise if the high_buffer_start is too
// high then the hcount is non zero and while closing the buffers the appropriate number of bytes
// in the high buffer is asigned to the filed hcount. Since the start address of the high buffer is
// known the bytes could be moved down
if ( (0x100000 LOW_BUFFER_SIZE) > ((ulg)high_buffer_start)) {
high_buffer_start = (uch *)(0x100000 LOW_BUFFER_SIZE);
mv->hcount = 0; /* say: we need not to move high_buffer */
}
else mv->hcount = -1;
mv->high_buffer_start = high_buffer_start;
// finally the high_buffer_start field is set to the varaible high_buffer_start
}

After the buffers are set gunzip() is invoked which decompresses the kernel Upon return, bytes_out has the number of bytes in the decompressed kernel.Finally close_output_buffer_if_we_run_high is invoked if high_loaded is non zero:

void close_output_buffer_if_we_run_high(struct moveparams *mv)
{
mv->lcount = bytes_out;
// if the all of decompressed kernel is in low buffer, lcount = bytes_out
if (bytes_out > LOW_BUFFER_SIZE) {
// if there is a part of the decompressed kernel in the high buffer, the lcount filed is set to
// the size of the low buffer and the hcount field contains the rest of the bytes
mv->lcount = LOW_BUFFER_SIZE;
if (mv->hcount) mv->hcount = bytes_out - LOW_BUFFER_SIZE;
// if the hcount field is non zero (made in setup_output_buffer_if_we_run_high)
// then the high buffer has to be moved doen and the number of bytes in the high buffer is
// in hcount
}
else mv->hcount = 0; // all the data is in the high buffer
}
Thus at the end of the the decompressed_kernel function the top of the stack has the addresses of the buffers and their sizes which is popped and the appropriate registers set for the move routine to move the entire kernel. After the move by the move_routine the kernel resides at 0x100000. If a small kernel is being decompressed then the setup_normal_output_buffer() is invoked from decompressed_kernel, which just initializes output_data to 0x100000 where the decompressed kernel would lie. The variable high_load is still 0 as setup_output_buffer_if_we_run_high() is not invoked. Decompression is done starting at address 0x100000. As high_load is 0, when decompressed_kernel returns in head.S, a zero is there in the eax. Thus the control jumps directly to 0x100000. Since the decompressed kernel lies there directly and the move routine need not be called.

Linux code taken from misc.c
Comment code added by us

[目錄]


setup.txt


一、按規定得有個頭,因此一開始是慣用的JMP;
二、頭裏邊內容很豐富,具體用法走着瞧;
三、自我檢測,不知道有什麼用,防僞造?防篡改?
四、若是裝載程序不對,只好死掉!如下終於走入正題;
五、獲取內存容量(使用了三種辦法,其中的E820和E801看不明白,int 15卻是老朋友了--應該是上個世紀80年代末認識的了,真佩服十年過去了,情意依舊,不過趕上一些不守規矩的BIOS,不知道還行不行);
六、將鍵盤重複鍵的重複率設爲最大,靈敏一點?
七、檢測硬盤,不懂,放這裏幹什麼?
八、檢測MCA總線(不要問我這是什麼);
九、檢測PS/2鼠標,用int 11,只是不知道爲什麼放這裏;
十、檢測電源管理BIOS;唉,書到用時方恨少,不懂的太多了,真很差意思;不過也沒有關係, 不懂的就別去動它就好了;如下要進入內核了;
十一、 在進入保護模式以前,能夠調用一個你提供的試模式下的過程,讓你最後在看她一眼,固然你要是不提供,那就有個默認的,無非是塞住耳朵閉上眼睛禁止任何中斷,包括著名的NMI ;
十二、設置保護模式起動後的例程地址, 你能夠寫本身的例程,但不是代替而是把它加在setup提供的例程的前面(顯示一個小鴨子?);
1三、若是內核是zImage, 將它移動到0x10000處;
1四、若是本身不在0x90000處,則移動到0x90000處;
1五、創建idt, gdt表;
1六、啓動A20;
1七、屏住呼吸,屏閉全部中斷;
1八、啓動!movw $1, %ax ; lmsw %ax; 好已經進入保護模式下,立刻進行局部調整;
1九、jmpi 0x100000, __KERNEL_CS,終於進入內核;

 

[目錄]


bootsect.txt


1.將本身移動到0x9000:0x0000處,爲內核調入留出地址空間;
2.創建運行環境(ss=ds=es=cs=0x9000, sp=0x4000-12),保證起動程序運行;
3.BIOS初始化0x1E號中斷爲軟盤參數表,將它取來保存備用;
4.將setup讀到0x9000:0x0200處;
5.測試軟盤參數一個磁道有多少個扇區(也沒有什麼好辦法,只能試試36, 18, 15, 9對不對了);
6.打印「Loading」;
7.讀入內核到0x1000:0000(若是是bzImage, 則將每一個64K移動到0x100000處,在實模式下,只能調用0x15號中斷了,這段代碼沒法放在bootsect中因此只能放在setup中,幸虧此時setup已經讀入了);
8.到setup去吧

[目錄]


用網卡從並口啓動(I386)


標題   用網絡卡從並口上啓動Linux(I386) [re: raoxianhong]
做者 raoxianhong (journeyman)
時間 10/07/01 12:31 PM

 

 

「十一」假期,哪兒也不去,作個程序博各位一笑。
=============================================
一、到底想幹什麼
   瞭解Linux的啓動過程,製做一個本身的Linux啓動程序,能夠增長對Linux的瞭解,還能學習PC機的啓動機制,增進對計算機結構的瞭解,加強對Linux內核學習的信心。也能夠在某些專用產品中使用(好比專用的服務器)。爲此,我嘗試在原來代碼的基礎上修改製做了一個用網絡卡從並口上啓動Linux的程序,以博一笑,其中有許多問題值得研究。
二、Linux對啓動程序的要求
   Linux(bzImage Kernel)對啓動程序的要求比較簡單,你只要可以創建一個啓動頭(setup.S),給出一些信息,而後將kernel(/usr/src/linux/arch/i386/boot/compressed/bvmlinux.out)調到
絕對地址0x100000(1M地址處),若是有initrd,則將它調到內存高端(離0x100000越遠越好,好比若是initrd小於4M,就能夠將它調到地址0xB00000,即12M處,相信如今已經不多有少於16M內存的機器了),
而後執行一些初始化操做,跳到內核處就好了。
   固然,提及來容易作起來還有點麻煩,如下分幾個問題解釋。
三、PC機開機流程--啓動程序放在何處
   PC機加電後,進入實模式,先進行自檢,而後初始化各個總線擴展設備(ISA, EISA,PCI,AGP),
所有初始化作完後,從當前啓動設備中讀一個塊(512字節)到07C0:0000處,將控制轉到該處。
   瞭解這個過程,咱們能夠決定將啓動程序放在何處:
      1)放在啓動設備的MBR(主啓動記錄中),好比磁盤的啓動扇區。這是通常的啓動方式。
      2)放在總線擴展設備的擴展rom中,好比網卡的boot rom就行,這裏製做的啓動程序就是放在網卡中,能夠支持
          16K字節。
      3)哪位高手可以修改ROMBIOS,讓BIOS在作完初始化後不要立刻從啓動設備讀數據,而是調用一段外面
          加入的程序(2K字節就夠了,固然也必須與修改後的BIOS一塊兒燒在BIOS ROM中),就能夠從BIOS啓動!
      4)先啓動一個操做系統,再在此操做系統中寫啓動程序(好比lodlin16就是從DOS中啓動Linux,好象中軟
         提供了一個從Windows下啓動Linux的啓動程序)。
四、操做系統放在何處
   操做系統(通常內核在500K-1M之間,加上應用程序能夠控制在2M之內,固然都通過壓縮了)的數據選擇餘地就大了,
能夠從軟盤、硬盤、CDROM、網絡、磁帶機、並口(軟件狗上燒個內核和應用程序?)、串口(外接你的設備)、
USB設備(?)、PCI擴展卡、IC卡等等上面來讀;各位還有什麼意見,提醒提醒。有位老兄說實在不行能夠用鍵盤啓動,
每次啓動時把內核敲進去,還有int 16h支持呢,作起來也不難,應該是最節省的方案了。
   反正一個原則是,在啓動程序中可以從該設備上讀就好了,這裏最簡單的就是並口了,簡單的端口操做,不需
要任何驅動程序支持,不須要BIOS支持,比磁盤還簡單(磁盤通常使用int 13h,主要是計算柱面啊、磁頭啊、磁道啊、
扇區啊好麻煩,幸虧有現成的源代碼,能夠學習學習)。
   好了,咱們挑個簡單的方案,將啓動代碼(bootsect.S+setup.S)放到網絡卡的boot rom中,內核數據和應用數據放
到另一臺計算機上,用並口提供。下面談談幾個相關的問題。
五、將數據移動到絕對地址處
   第一個問題,咱們獲得數據,由於是在實模式下,因此通常是放在1M地址空間內,怎樣將它移動到指定的地方去,
在setup.S 的源代碼中,使用了int 15h(87h號功能)。這裏將該段代碼稍加改動,作了些假設,列到下面:
流程是:
        if (%cs:move_es==0)/*因爲使用前move_es初始化爲0,所以這是第一次調用,此時es:bx是要移動的數據
                                存放處bx=0,es低四爲位爲零表示es:bx在64K邊界上,fs的低8位指定目的地地址,
                                也以64K字節爲單位,用不着那麼精確,以簡化操做*/
        {
                將es右移四位,獲得64K單位的8位地址(這樣一來,最多隻能將數據移動到16M如下了),做爲源數據
                描述符中24位地址的高8位,低16位爲零。
                將fs的低8位做爲目的地的描述符中24位地址的高8位,一樣,它的低16位爲零。
                將es存放在move_es中,es天然不會是零,所以之後再調用該例程時就進行正常的移動操做了。
                ax清零返回。
        }
        else
        {
                if (bx==0)/*bx爲零,表示數據已經滿64K了,應該進行實際的移動*/
                {
                        調用int15h 87h號功能,進行實際的數據移動(64K, 0x8000個16字節塊)。
                        目的地址(24位)高8位增一,日後走64K
                        ax = 1
                        return;
                }
                else
                {
                        ax = 0;
                        return;
                }
        }


# we will move %cx bytes from es:bx to %fs(64Kbytes per unit)
# when we first call movetohigh(%cs:move_es is zero),
# the es:bx and %edx is valid
# we configure the param first
# follow calls will move data actually
# %ax return 0 if no data really moved, and return 1 if there is data
# really to be moved
#
movetohigh:
        cmpw        $0, %cs:move_es
        jnz        move_second

        # at this point , es:bx(bx = 0) is the source address
        # %edx is the destination address

        movb        $0x20, %cs:type_of_loader
        movw        %es, %ax
        shrw        $4, %ax
        movb        %ah, %cs:move_src_base+2
        movw        %fs, %ax
        movb        %al, %cs:move_dst_base+2
        movw        %es, %ax
        movw        %ax, %cs:move_es
        xorw        %ax, %ax
        ret                                        # nothing else to do for now

move_second:
        xorw        %ax, %ax
        testw        %bx, %bx
        jne        move_ex
        pushw        %ds
        pushw        %cx
        pushw        %si
        pushw        %bx

        movw        $0x8000, %cx                        # full 64K, INT15 moves words
        pushw        %cs
        popw        %es
        leaw        %cs:move_gdt, %si
        movw        $0x8700, %ax
        int        $0x15
        jc        move_panic                        # this, if INT15 fails

        movw        %cs:move_es, %es                # we reset %es to always point
        incb        %cs:move_dst_base+2                # to 0x10000
        popw        %bx
        popw        %si
        popw        %cx
        popw        %ds
        movw        $1, %ax
move_ex:
        ret

move_gdt:
        .word        0, 0, 0, 0
        .word        0, 0, 0, 0

move_src:
        .word        0xffff

move_src_base:
        .byte        0x00, 0x00, 0x01                # base = 0x010000
        .byte        0x93                                # typbyte
        .word        0                                # limit16,base24 =0

move_dst:
        .word        0xffff

move_dst_base:
        .byte        0x00, 0x00, 0x10                # base = 0x100000
        .byte        0x93                                # typbyte
        .word        0                                # limit16,base24 =0
        .word        0, 0, 0, 0                        # BIOS CS
        .word        0, 0, 0, 0                        # BIOS DS

move_es:
        .word        0

move_panic:
        pushw        %cs
        popw        %ds
        cld
        leaw        move_panic_mess, %si
        call        prtstr

move_panic_loop:
        jmp        move_panic_loop

move_panic_mess:
        .string        "INT15 refuses to access high mem, giving up."


六、用並口傳輸數據
   用並口傳輸數據,能夠從/usr/src/linux/driver/net/plip.c中抄一段,咱們採用半字節協議,
並口線鏈接參考該文件。字節收發過程以下:

#define PORT_BASE                0x378

#define data_write(b) outportb(PORT_BASE, b)
#define data_read() inportb(PORT_BASE+1)

#define OK                        0
#define TIMEOUT                        1
#define FAIL                        2

int sendbyte(unsigned char data)
{
        unsigned char c0;
        unsigned long cx;

        data_write((data & 0x0f));
        data_write((0x10 | (data & 0x0f)));
        cx = 32767l * 1024l;
        while (1) {
                c0 = data_read();
                if ((c0 & 0x80) == 0)
                        break;
                if (--cx == 0)
                        return TIMEOUT;
        }
        data_write(0x10 | (data >> 4));
        data_write((data >> 4));
        cx = 32767l * 1024l;
        while (1) {
                c0 = data_read();
                if (c0 & 0x80)
                        break;
                if (--cx == 0)
                        return TIMEOUT;
        }
        return OK;
}

int rcvbyte(unsigned char * pByte)
{
        unsigned char c0, c1;
        unsigned long cx;

        cx = 32767l * 1024l;
        while (1) {
                c0 = data_read();
                if ((c0 & 0x80) == 0) {
                        c1 = data_read();
                        if (c0 == c1)
                                break;
                }
                if (--cx == 0)
                        return TIMEOUT;
        }
        *pByte = (c0 >> 3) & 0x0f;
        data_write(0x10); /* send ACK */
        cx = 32767l * 1024l;
        while (1) {
                c0 = data_read();
                if (c0 & 0x80) {
                        c1 = data_read();
                        if (c0 == c1)
                                break;
                }
                if (--cx == 0)
                        return TIMEOUT;
        }
        *pByte |= (c0 << 1) & 0xf0;
        data_write(0x00); /* send ACK */
        return OK;
}

爲了可以在setup.S下收字符,特將字符接收子程序該爲AT&T彙編語法
(也沒有什麼好辦法,在DOS下用TURBO C 2.0將上述代碼編譯成彙編
代碼,而後手工轉換成AT&T格式,聽說有程序能夠自動進行這樣的轉換,
有誰用過請指教):

rcvbyte:
        pushw        %bp
        movw        %sp, %bp
        subw        $6, %sp
        movw        $511, -2(%bp)
        movw        $-1024, -4(%bp)
        jmp        .L13
.L15:
        movw        $889, %dx
        inb        %dx, %al
        movb        %al, -6(%bp)
        testb        $128, -6(%bp)
        jne        .L16
        inb        %dx, %al
        movb        %al, -5(%bp)
        movb        -6(%bp), %al
        cmpb        -5(%bp), %al
        jne        .L17
        jmp        .L14
.L17:
.L16:
        subw        $1, -4(%bp)
        sbbw        $0, -2(%bp)
        movw        -2(%bp), %dx
        movw        -4(%bp), %ax
        orw        %ax, %dx
        jne        .L18
        movw        $1, %ax
        jmp        .L12
.L18:
.L13:
        jmp        .L15
.L14:
        movb        -6(%bp), %al
        shrb        $1, %al
        shrb        $1, %al
        shrb        $1, %al
        andb        $15, %al
        movw        4(%bp), %bx
        movb        %al, (%bx)
        movb        $16, %al
        movw        $888, %dx
        outb        %al, %dx
        movw        $511, -2(%bp)
        movw        $-1024, -4(%bp)
        jmp        .L19
.L21:
        movw        $889, %dx
        inb        %dx, %al
        movb        %al, -6(%bp)
        testb        $128, %al
        je        .L22
        inb        %dx, %al
        movb        %al, -5(%bp)
        movb        -6(%bp), %al
        cmpb        -5(%bp), %al
        jne        .L23
        jmp        .L20
.L23:
.L22:
        subw        $1, -4(%bp)
        sbbw        $0, -2(%bp)
        movw        -2(%bp), %dx
        movw        -4(%bp), %ax
        orw        %ax, %dx
        jne        .L24
        movw        $1, %ax
        jmp        .L12
.L24:
.L19:
        jmp        .L21
.L20:
        movb        -6(%bp), %al
        shlb        $1, %al
        andb        $240, %al
        movw        4(%bp), %bx
        orb        %al, (%bx)
        xorw        %ax, %ax
        movw        $888, %dx
        outb        %al, %dx
        jmp        .L12
.L12:
        movw        %bp, %sp
        popw        %bp
        ret

可以收發字符還不行,做爲協議,總得知道數據的起始和結束,也應該進行簡單的檢錯。這裏採用
字符填充方式進行數據包編碼,用‘/’表示轉義字符,數據包頭用/H表示,數據包結束用/T表示
若是數據中有'/',則用//表示(從printf的格式串中學來的),數據包後面跟一個字節的校驗和,
這樣就能夠收發數據包了,具體程序以下:

int rcvpack(unsigned char * pData, int * pLength)
{
        int ret;
        int length;
        unsigned char checksum;
        int maxlength;
        int status;
        maxlength = *pLength + 1;
        if (maxlength<=0)
                return FAIL;
        if (pData == NULL)
                return FAIL;
        checksum = 0;
        length = 0;
        status = 0;
        while (1)
        {
                unsigned char ch;
                int count;
                count = 10;
                while (1)
                {
                if ((ret = rcvbyte(&ch)) != OK)
                {
                        count--;
                        if (count==0)
                        {
                                printf("/nReceive byte timeout/n");
                                return ret;
                        }
                }
                        else
                                break;
                }
                switch (status)
                {
                        case 0:
                        {
                                if (ch == '//')
                                {
                                        status = 1;
                                }
                        }
                        break;
                        case 1:
                        {
                                if (ch == 'H')
                                        status = 2;
                                else
                                        status = 0;
                        }
                        break;
                        case 2:
                        {
                                if (ch == '//')
                                {
                                        status = 3;
                                }
                                else
                                {
                                        length ++;
                                        if (length>maxlength)
                                        {
                                                printf("Buffer overflow(%d>%d)/n", length, maxlength);
                                                return FAIL;
                                        }
                                        *pData++ = ch;
                                        checksum += ch;
                                }
                        }
                        break;
                        case 3:
                        {
                                if (ch == '//')
                                {
                                    length++;
                                    if (length>maxlength)
                                    {
                                        printf("Buffer overflow (%d>%d)/n", length, maxlength);
                                        return FAIL;
                                    }
                                    checksum += ch;
                                    *pData++ = ch;
                                    status = 2;
                                }
                                else
                                if (ch =='T')
                                {
                                        unsigned char chk;
                                        *pLength = length;
                                        if (rcvbyte(&chk)!=OK)
                                                return FAIL;
                                        if (checksum==chk)
                                        {
                                                return OK;
                                        }
                                        else
                                        {
                                                printf("ERROR: Checksum is nozero(%d-%d)/n", checksum,chk);
                                                return FAIL;
                                        }
                                }
                                else
                                {
                                        printf("ERROR: a '//' or 'T' expected('%c')!/n ", ch);
                                        return FAIL;
                                }
                        }
                }
        }
}


int sendpack(unsigned char * pData, int length)
{
        int ret;
        unsigned char checksum;
        checksum = 0;
        if (length<=0)
                return OK;
        if ((ret = sendbyte('//')) != OK)
                return 1;
        if ((ret = sendbyte('H')) != OK)
                return 2;
        while (length>0)
        {
                unsigned char ch;
                ch = *pData++;
                checksum += ch;
                if ((ret = sendbyte(ch)) != OK)
                        return 3;
                if (ch == '//')
                {
                        if ((ret = sendbyte(ch)) != OK)
                                return 4;
                }
                length--;
        }

        if ((ret = sendbyte('//')) != OK)
                return 5;
        if ((ret = sendbyte('T')) != OK)
                return 6;
        if ((ret = sendbyte(checksum)) != OK)
                return 7;
        return OK;
}

一樣,也將rcvpack改爲AT&T彙編(減小了幾個printf語句):

chbuffer:
        .byte 0
overflow:
        .string "Buffer overflow..."
rcvpack:
        pushw        %bp
        movw        %sp, %bp
        subw        $12, %sp
        pushw        %si
        movw        4(%bp), %si
        movw        6(%bp), %bx
        movw        (%bx), %ax
        incw         %ax
        movw        %ax, -6(%bp)
        cmpw        $0, -6(%bp)
        jg        .L26
        leaw        overflow, %si
        call        prtstr
        movw        $2, %ax
        jmp        .L25
.L26:
        orw        %si, %si
        jne        .L27
        movw        $2, %ax
        jmp        .L25
.L27:
        movb        $0,-8(%bp)
        movw        $0, -10(%bp)
        movw        $0, -4(%bp)
        jmp        .L28
.L30:
        movw        $10, -2(%bp)
        jmp        .L31
.L33:
#        movw        -4(%bp), %ax
#        addb        $'0', %al
#        call        prtchr
        leaw        chbuffer, %ax
        pushw        %ax
        call        rcvbyte
        popw        %cx
        movw        %ax, -12(%bp)
        orw         %ax, %ax
        je        .L34
        decw        -2(%bp)
        cmpw        $0, -2(%bp)
        jne        .L35
        movw        -12(%bp), %ax
        jmp        .L25
.L35:
        jmp        .L36
.L34:
        jmp        .L32
.L36:
.L31:
        jmp        .L33
.L32:
        pushw        %si
        leaw        chbuffer, %si
        movb        (%si), %al
        movb        %al, -7(%bp)
        popw        %si
#        call        prtchr
        movw         -4(%bp), %ax
        cmpw        $3, %ax
        jbe        .L58
        jmp        .L56
.L58:
        cmpw        $0, %ax
        je        .L38
        cmpw        $1, %ax
        je        .L40
        cmpw        $2, %ax
        je        .L43
        cmpw        $3, %ax
        je        .L47
        jmp        .L56
.L38:
        cmpb        $92, -7(%bp)
        jne        .L39
        movw        $1, -4(%bp)
.L39:
        jmp         .L37
.L40:
        cmpb        $72, -7(%bp)
        jne        .L41
        movw        $2, -4(%bp)
        jmp        .L42
.L41:
        movw        $0, -4(%bp)
.L42:
        jmp        .L37
.L43:
        cmpb        $92, -7(%bp)
        jne        .L44
        movw        $3, -4(%bp)
        jmp         .L45
.L44:
        incw        -10(%bp)
        movw        -10(%bp), %ax
        cmpw        -6(%bp), %ax
        jle        .L46
        movw        $2, %ax
        jmp        .L25
.L46:
        movb        -7(%bp), %al
        movb        %al, (%si)
        incw        %si
        movb         -7(%bp), %al
        addb        %al, -8(%bp)
.L45:
        jmp        .L37
.L47:
        cmpb        $92, -7(%bp)
        jne        .L48
        incw        -10(%bp)
        movw        -10(%bp), %ax
        cmpw        -6(%bp), %ax
        jle        .L49
        movw        $2, %ax
        jmp        .L25
.L49:
        movb        -7(%bp), %al
        addb        %al, -8(%bp)
        movb        -7(%bp), %al
        movb        %al, (%si)
        incw        %si
        movw        $2, -4(%bp)
        jmp        .L50
.L48:
        cmpb        $84, -7(%bp)
        jne        .L51
        movw        -10(%bp), %ax
        movw        6(%bp), %bx
        movw        %ax, (%bx)
        leaw        chbuffer, %ax
        pushw        %ax
        call        rcvbyte
        popw        %cx
        orw        %ax, %ax
        je        .L52
        movw        $2, %ax
        jmp        .L25
.L52:
        movb        -8(%bp), %al
        cmpb        chbuffer, %al
        jne        .L53
        xorw        %ax, %ax
        jmp        .L25
        jmp        .L54
sChecksumFailed:
        .string "Checksum error!"
.L53:
        leaw        sChecksumFailed, %si
        call         prtstr
        movw        $2, %ax
        jmp        .L25
.L54:
        jmp        .L55
.L51:
        movw        $2, %ax
        jmp        .L25
.L55:
.L50:
.L56:
.L37:
.L28:
        jmp         .L30
.L29:
.L25:
        popw        %si
        movw        %bp, %sp
        popw        %bp
        ret

好了,萬事具有了,先用上面的c代碼寫另一臺計算機上的「服務」程序(也用來測試),這臺
計算機運行DOS,用TURBO C 2.0編譯運行:
運行時將initrd.img和內核編譯後的/usr/src/linux/arch/i386/boot/compressed/bvmlinux.out
拷貝到該計算機的c:/下,而後帶參數 s c:/bvmlinux.out c:/initrd.img運行便可。
至於啓動程序,還得進行少量修改,才能燒到boot rom 中,見後面的說明。

int main(int argc, char* argv[])
{
        FILE* pFile;
        int count = 2;
        if (argc<3)
        {
                printf("Usage testspp [s | r] /n");
                return 1;
        }
        while(count        {
        if (argv[1][0] == 's')
                pFile = fopen(argv[count], "rb");
        else
                pFile = fopen(argv[count], "wb");
        if (pFile==NULL)
        {
                printf("Can't open/create file %s/n", argv[2]);
                return 2;
        }
        if (argv[1][0]=='r')/*receive*/
        {
                unsigned long filesize;
                char buffer[10244];
                int length;
                /*get filelength */
                length = 10244;

                printf("Receiving filesize package/n");
                while( (rcvpack(buffer, &length)!=OK) && (length!=4))
                        length = 10244;
                filesize = *(long*)buffer;
                printf("file size is:%ld/n", filesize);

                while (filesize>0)
                {
                        length = 10244;
                        if (rcvpack(buffer, &length) != OK)
                        {
                                printf("Receive data package failed/n");
                                return 0;
                        }
                        if (length>0)
                                fwrite(buffer, 1, length, pFile);
                        filesize-=length;
                        printf("/r%ld Bytes Left         ", filesize);
                }
        }
        else/*send*/
        {
                unsigned long filesize;
                /*send file length*/
                unsigned long stemp;
                int ret;
                fseek(pFile, 0, 2);
                filesize = ftell(pFile);
                fseek(pFile, 0, 0);
                printf("/nfile size is:%ld/n", filesize);
                /*
                while ((ret = sendpack((char *)&filesize, 4)) != OK)
                {
                        printf("send file size failed(%d)/n", ret);
                }
                */
                while (filesize>0)
                {
                        char buffer[10240];
                        long size;
                        int ret;
                        size = fread(buffer, 1, 10240, pFile);
                        if ((ret = sendpack(buffer, size)) != OK)
                        {
                                printf("Send data package failed(%d)/n", ret);
                                return 0;
                        }
                        filesize -= size;
                        printf("/r/t%ld Bytes Left", filesize);
                }
        }
        fclose(pFile);
        count++;
        }/*while*/
        return 0;
}

五、對bootsect.S的修改
   目前的bootsect.S ,主要的問題是,它是從軟盤上讀數據,將這些代碼換成對rcvpack的調用便可,
另外,它不支持調入initrd,應該增長相應的代碼。問題在於,bootsect.S中沒有那麼多空間來放rcvpack
相關的代碼(畢竟只有512字節,固然,若是燒在boot rom中,就不存在這個問題了,可是用軟盤調試時
就不行了,所以乾脆編制load_kernel和load_initrd放在setup.S中,而後在bootsect.S中進行回調便可。
bootsect.S 修改以下(只給出修改部分):
.....
.....
ok_load_setup:
        call        kill_motor
        call        print_nl
# Now we will load kernel and initrd
loading:
# 先打印Loading字符
        movw        $INITSEG, %ax
        movw        %ax, %es                # set up es
        movb        $0x03, %ah                # read cursor pos
        xorb        %bh, %bh
        int        $0x10
        movw        $22, %cx
        movw        $0x0007, %bx                # page 0, attribute 7 (normal)
        movw    $msg1, %bp
        movw    $0x1301, %ax                # write string, move cursor
        int        $0x10                        # tell the user we're loading..
load_kernel_img:
# 將load_kernel函數的指針放到0x22C處這裏進行調用就好了(軟盤啓動過程當中,此前已經將setup.S
# 從磁盤上調到bootsect.S,即0x0200以後,注意setup.S的頭部是一張表,這裏「提早」消費了)
# 0x22C is the load kernel routine
        bootsect_readimage = 0x22C
        lcall        bootsect_readimage
load_initrd_img:
# 將load_initrd函數的指針放到0x220處
# 0x220 if the load initrd routine
        bootsect_readinitrd = 0x220
        lcall        bootsect_readinitrd

# After that (everything loaded), we jump to the setup-routine
# loaded directly after the bootblock:
        ljmp        $SETUPSEG, $0
......
......
六、對setup.S的修改
   對setup.S進行修改,主要是:修改setup.S頭部,增長load_kernel和load_initrd函數等,具體以下。
    修改setup.S頭部以下(爲好看,這裏刪除了原來的部分註釋):

start:
                        jmp        trampoline
                        .ascii        "HdrS"                # header signature
                        .word        0x0202                # header version number (>= 0x0105)
realmode_swtch:        .word        0, 0                # default_switch, SETUPSEG
start_sys_seg:        .word        SYSSEG
                        .word        kernel_version        # pointing to kernel version string
type_of_loader:        .byte        0
loadflags:
LOADED_HIGH        = 1
                        .byte        LOADED_HIGH   # 只支持bzImage
setup_move_size:         .word  0x8000
code32_start:                                # here loaders can put a different
                        .long        0x100000        # 0x100000 = default for big kernel
ramdisk_image:        .long        0xB00000        # ramdisk 調到12M處
ramdisk_size:                .long        0                # 由load_initrd來設置長度
bootsect_kludge:
                        .word  load_initrd, SETUPSEG #0x220, 放置load_initrd函數的指針
heap_end_ptr:                .word        modelist+1024        pad1:                .word        0
cmd_line_ptr:                .long 0
load_kernel_call:
                        .word  load_kernel, SETUPSEG
trampoline:                call        start_of_setup

                        .space        1024

load_kernel和load_initrd:

load_imsg:
        .byte 13, 10
        .string "Load INITRD from PARPort(378)"
load_kmsg:
        .byte 13, 10
        .string        "Load Kernel From PARPort(378)"
reading_suc:
        .string        "."
reading_failed:
        .string " failed"
read_len:
        .word 0, 0
read_total:
        .word 0, 0
read_buffer:
        # 如何在AT&T語法中完成intel語法中的 db 1280 dup(0),那位請指教
        # AT&T彙編的語法何處尋?
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"
        .string "012345678901234567890123456789012345678901234567890123456789"

load_initrd:
        pushw        %ds
        pushw        %es
        pushw        %cs
        popw        %ds
        pushw        %cs
        popw        %es
        cld
        leaw        load_imsg, %si
        call        prtstr                                # 打印提示
        movw        $0x1000, %ax
        movw        %ax, %es
        xorw        %bx, %bx
        movw        $0x00B0, %ax                        # initrd數據先調到0x1000:0000處,
                                                # 滿64K即移動到12M(0xB00000)處
        movw        %ax, %fs
        movw        $0, %cs:move_es
        movl        $0, %cs:read_total
        call        movetohigh                        # 初始化數據移動部分
        call        .ld_img                        # 從並口上讀入一個文件並移動到指定位置
        movl        %cs:read_total, %eax
        movl        %eax, %cs:ramdisk_size        # 設置ramdisk_size和ramdisk_image
        movl        $0x00B00000, %eax
        movl        %eax, %cs:ramdisk_image
        popw        %es
        popw        %ds
        lret

load_kernel:
        pushw        %ds
        pushw        %es
        pushw        %cs
        popw        %ds
        pushw   %cs
        popw        %es
        cld
        leaw        load_kmsg, %si
        call        prtstr
        movw        $0x1000, %ax
        movw        %ax, %es
        xorw        %bx, %bx
        movw        $0x0010, %ax
        movw        %ax, %fs
        movw        $0, %cs:move_es
        movl        $0, %cs:read_total
        call        movetohigh
        call        .ld_img
        popw        %es
        popw        %ds
        lret

.ld_img:
.ld_nextpack:
        pushw        %bx
        pushw        %es
        leaw         read_len, %si
        movw        $1124, %ax
        movw        %ax, (%si)
        pushw        %si
        leaw        read_buffer, %ax
        pushw        %ax
        movw        %bx, %ax
        call        rcvpack                # 調用rcpack接收一個數據包read_buffer中
        popw        %cx
        popw        %cx
        popw        %es
        popw        %bx
        cmpw        $0, %ax                # 成功?
        je        .ld_suc
        leaw        reading_failed, %si
        call        prtstr
.ld_panic:
        jmp        .ld_panic                # 失敗則死循環
.ld_suc:
        leaw        read_buffer, %si
        movw        %bx, %di
        movw        $256, %cx                # move 1024 bytes
        rep
        movsl                                # 從read_buffer移動到es:bx處,強制假定一個數據包長度
                                        # 就是1024字節,最後一個數據包除外。
        addw        $1024, %bx                # 更新bx, 若是bx加到零,則表示已經滿64K,後面的調用中
        call         movetohigh                # 進行實際的數據移動
        movw        %ax, %dx                #
        cmpw        $0, %ax                # 若是進行了64K數據移動,就打印一個'.'
        je        .ld_1
        leaw        reading_suc, %si
        call         prtstr
.ld_1:
        leaw        read_len, %si
        xorl        %eax, %eax
        movw        (%si), %ax
        addl        %eax, %cs:read_total
        cmpw        $1024, %ax                 # 更新收到數據總字節數,若是收到的字節數少於1024,則表示
                                        # 收到最後一個數據包,這得冒點風險,萬一最後一個數據包恰好
                                        # 是1024字節,怎麼辦好呢?賭一把吧!
        jb        .ld_lastestpack
        jmp        .ld_nextpack                # 接着接收下一個數據包
.ld_lastestpack:
        # 最後一個數據包收到後,不見得滿64K,此時應該強制數據移動
        cmpw        $0, %dx
        jne        .ld_exit
        xorw        %bx, %bx
        call        movetohigh
.ld_exit:
        ret

七、用軟盤進行調試,將啓動程序燒到bootrom中
        好了,大功告成,對內核進行配置,而後make bzImage,將bvmlinux.out拷貝到「服務器」上,創建
initrd也放在「服務器」上,而後放張軟盤在軟驅中,dd if=/usr/src/linux/arch/i386/boot/bzImage of=
/dev/fd0 count=32將bootsect.S+setup.S部分拷貝到軟盤上,從新啓動(先鏈接好並口線)。啓動後再在
「服務器」上啓動文件「服務」程序,終於能夠將Linux從並口上啓動了!
        作少許調整(主要是去掉讀setup.S部分的代碼),便可以將此bzImage的前8(16?)K寫在一個文件中,
處理成boot rom映象,燒到boot rom中,插到網絡卡上,啓動機器便可。這就是用網絡卡從並口上啓動Linux。

 

 

標題   Re: 用網絡卡從並口上啓動Linux(I386) [re: raoxianhong]
做者 raoxianhong (journeyman)
時間 10/09/01 11:30 AM

 

網絡上說能夠將Bootrom寫到BIOS中去,可是沒有實驗成功,不知道有什麼講究,哪位可曾試過?
尋找文件 cbrom.pdf

 

 


標題   推薦兩篇講述啓動過程的文章 [re: feiyunw]
做者 raoxianhong (journeyman)
時間 10/11/01 09:08 AM

 

http://www.pcguide.com/ref/mbsys/bios/boot.htm
http://www2.csa.iisc.ernet.in/~kvs/LinuxBoot.html

 

 


標題   Re: 386 boot代碼分析 [re: feiyunw]
做者 raoxianhong (member)
時間 10/25/01 05:09 PM
附加文件 181431-bootrom.zip

 

有幾位老兄Mail問網卡啓動的啓動代碼問題,這裏總結以下:
1.系統自檢完畢後在ROM空間中找(好象是2Kbytes爲單位),若是某一段的前兩表字節是0x55AA,那麼第三個字節做爲ROM程序的大小(512字節爲單位)。而後將該段空間中的全部字節相加(計算校驗和),結果爲零時表示ROM程序有效。此時BIOS用一個長調用(lcall),調用該塊的第四個字節起始處(天然該用lret返回)。
2.有個問題原來一直不明白,若是此時某個啓動網卡啓動系統,可是後面還有帶ROM的卡(好比PCI),那麼該段ROM程序豈不是沒有機會運行了嗎,固然,若是不運行任何設備的擴展ROM,不知道Linux內會不會有問題!後來查資料得知,實際上製做網卡啓動程序時尚未這麼簡單。
3.事實上,系統在自檢及運行全部的擴展硬件檢測以後,是用int 19h啓動操做系統的!所以在擴展ROM中不直接啓動操做系統,而是將操做系統啓動代碼做爲int 19h的中斷調用(其實也不用返回,操做系統沒有必要返回)代碼就好了。
明白這一點後,製做一個網卡啓動程序就容易多了,具體請看某個網卡的啓動源代碼便可,附件中有一個,記不住是從哪裏抄來的了!

 

 


標題   通用的網絡卡bootrom處理程序 [re: feiyunw]
做者 raoxianhong (member)
時間 12/06/01 08:05 PM

 

Bootrom寫好後要進行一些處理才能燒到EPROM中去。這裏提供一段代碼能夠完成這個功能,上面講的用並口啓動Linux的程序就是這麼處理的。
基本的想法是,寫一個通用的啓動代碼載入程序(stub),將bootsect.S+setup.S(也就是bzImage的前面一段)設置成0x19號中斷的中斷向量。在外面寫一段代碼將該段代碼和啓動代碼進行合併,生成合法的bootrom映象就,能夠燒到bootrom中去,在網絡卡上啓動。

下面是通用的啓動代碼載入程序:

 

.code16
RomHeader:
        .byte 0x55, 0xaa #啓動ROM標誌
RomPageCount:
        .byte 0x20  #假定bootrom是16K bytes

RomCode:
        pushw %es
        pushw %bx
        pushw %ax

        movb  $0xc1, %al
        call  IntVectAddr
        movw  $0x6a6e, %ax
        cmpw  %es:(%bx), %ax
        jz    RomBootInit_x
        movw  %ax, %es:(%bx)
        movw  $0xc019, %ax
        call  MoveIntVector
        movw  $RomBootVect, %bx
        pushw %cs
        popw  %es
        call  SetIntVector
RomBootInit_x:
        popw  %ax
        popw  %bx
        popw  %es
        lret

IntVectAddr:
        xorw        %bx,%bx
        movw        %bx,%es
        movb        %al,%bl
        addw        %bx,%bx
        addw        %bx,%bx
        ret

GetIntVector:
        call        IntVectAddr
GetIntVect_1:
                     les        %es:(%bx), %bx
        ret

SetIntVector:
        pushf                        #; entry AL=vector to set, ES:BX=value
        pushw        %es                #; exit: vector modified
        pushw        %bx                #; all registers preserved
        call        IntVectAddr
        cli
        popw        %es:(%bx)
        addw        $2, %bx
        popw        %es:(%bx)
        subw        $2, %bx
        popf
        jmp        GetIntVect_1

MoveIntVector:
        call        GetIntVector        #; entry AL=vect to get, AH=vect to set
        xchgb        %al,%ah                #; exit: vector set, ES:BX=vector value
        call        SetIntVector        #; other registers preserved
        xchgb        %al,%ah
        ret

RomBootVect:
        pushw   %cs
        popw        %ds
        movw        $0x07c0, %ax
        movw        %ax, %es
        movw        $BootCode, %si
        subw        %di, %di
        movw        $8192, %cx
        cld
        rep
        movsw
        ljmp        $0x07c0, $0
        lret

.org        0x0200
BootCode:

 


在Linux下的編譯方法與bootsect.S的編譯方法同樣,編譯成可執行文件後,好比放在bootx文件中。
內核編譯後(make bzImage,支持上面所說的啓動方式),獲得bzImage文件。

下面是將這兩個文件複合在一塊兒獲得bootrom映象的程序:


/* mkbtrom.c */

int main(int argc, char* argv[])
{
        char buf[16384];
        char ch;
        int i;
        if (argc<4)
        {
                printf("Usage: mkbtrom   /n");
                return 1;
        }
        FILE * pFile;
        pFile = fopen(argv[1], "rb");
        if (pFile==NULL)
        {
                printf("File %s open failed/n", argv[1]);
                return 2;
        }
        fread(buf, 1, 512, pFile);
        fclose(pFile);
        pFile = fopen(argv[2], "rb");
        if (pFile==NULL)
        {
                printf("File %s open failed/n", argv[2]);
                return 2;
        }
        fread(&buf[512], 1, 16384-512-1, pFile);
        fclose(pFile);
        ch = 0;
        for (i = 0;i<18383;i++)
                ch += buf[ i ];
        buf[16383] = -ch;
        pFile = fopen(argv[3], "wb");
        fwrite(buf, 1, 16384, pFile);
        fclose(pFile);
        return 0;
}

編譯成執行文件後,運行mkbtrom bootx bzImage boot16k.bin後,boot16k.bin就能夠燒到eprom中,從網絡卡中啓動了。

[目錄]


內核解壓


概述

----

1) Linux的初始內核映象以gzip壓縮文件的格式存放在zImage或bzImage之中, 內核的自舉

代碼將它解壓到1M內存開始處. 在內核初始化時, 若是加載了壓縮的initrd映象, 內核會將

它解壓到內存盤中, 這兩處解壓過程都使用了lib/inflate.c文件.

 

2) inflate.c是從gzip源程序中分離出來的, 包含了一些對全局數據的直接引用, 在使用時

須要直接嵌入到代碼中. gzip壓縮文件時老是在前32K字節的範圍內尋找重複的字符串進行

編碼, 在解壓時須要一個至少爲32K字節的解壓緩衝區, 它定義爲window[WSIZE].

inflate.c使用get_byte()讀取輸入文件, 它被定義成宏來提升效率. 輸入緩衝區指針必須

定義爲inptr, inflate.c中對之有減量操做. inflate.c調用flush_window()來輸出window

緩衝區中的解壓出的字節串, 每次輸出長度用outcnt變量表示. 在flush_window()中, 還必

須對輸出字節串計算CRC而且刷新crc變量.  在調用gunzip()開始解壓以前, 調用makecrc()

初始化CRC計算表. 最後gunzip()返回0表示解壓成功.

 

3) zImage或bzImage由16位引導代碼和32位內核自解壓映象兩個部分組成. 對於zImage, 內

核自解壓映象被加載到物理地址0x1000, 內核被解壓到1M的部位. 對於bzImage, 內核自解

壓映象被加載到1M開始的地方, 內核被解壓爲兩個片斷, 一個起始於物理地址0x2000-0x90000,

另外一個起始於高端解壓映象以後, 離1M開始處不小於低端片斷最大長度的區域. 解壓完成後,

這兩個片斷被合併到1M的起始位置.

 

解壓根內存盤映象文件的代碼

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

 

; drivers/block/rd.c

#ifdef BUILD_CRAMDISK

 

/*

* gzip declarations

*/

 

#define OF(args)  args        ; 用於函數原型聲明的宏

 

#ifndef memzero

#define memzero(s, n)     memset ((s), 0, (n))

#endif

 

typedef unsigned char  uch;        定義inflate.c所使用的3種數據類型

typedef unsigned short ush;

typedef unsigned long  ulg;

 

#define INBUFSIZ 4096                用戶輸入緩衝區尺寸

#define WSIZE 0x8000    /* window size--must be a power of two, and */

                        /*  at least 32K for zip's deflate method */

 

static uch *inbuf;        用戶輸入緩衝區,與inflate.c無關

static uch *window;        解壓窗口

 

static unsigned insize;  /* valid bytes in inbuf */

static unsigned inptr;   /* index of next byte to be processed in inbuf */

static unsigned outcnt;  /* bytes in output buffer */

static int exit_code;

static long bytes_out;        總解壓輸出長度,與inflate.c無關

static struct file *crd_infp, *crd_outfp;

 

#define get_byte()  (inptr

/* Diagnostic functions (stubbed out) */ 一些調試宏

#define Assert(cond,msg)

#define Trace(x)

#define Tracev(x)

#define Tracevv(x)

#define Tracec(c,x)

#define Tracecv(c,x)

 

#define STATIC static

 

static int  fill_inbuf(void);

static void flush_window(void);

static void *malloc(int size);

static void free(void *where);

static void error(char *m);

static void gzip_mark(void **);

static void gzip_release(void **);

 

#include "../../lib/inflate.c"

 

static void __init *malloc(int size)

{

        return kmalloc(size, GFP_KERNEL);

}

 

static void __init free(void *where)

{

        kfree(where);

}

 

static void __init gzip_mark(void **ptr)

{

        ; 讀取用戶一個標記

}

 

static void __init gzip_release(void **ptr)

{

        ; 歸還用戶標記

}

 

 

/* ===========================================================================

* Fill the input buffer. This is called only when the buffer is empty

* and at least one byte is really needed.

*/

static int __init fill_inbuf(void) 填充輸入緩衝區

{

        if (exit_code) return -1;

 

        insize = crd_infp->f_op->read(crd_infp, inbuf, INBUFSIZ,

 

        if (insize == 0) return -1;

 

        inptr = 1;

 

        return inbuf[0];

}

 

/* ===========================================================================

* Write the output window window[0..outcnt-1] and update crc and bytes_out.

* (Used for the decompressed data only.)

*/

static void __init flush_window(void) 輸出window緩衝區中outcnt個字節串

{

    ulg c = crc;         /* temporary variable */

    unsigned n;

    uch *in, ch;

 

    crd_outfp->f_op->write(crd_outfp, window, outcnt,

    in = window;

    for (n = 0; n             ch = *in++;

            c = crc_32_tab[((int)c ^ ch)  0xff] ^ (c >> 8); 計算輸出串的CRC

    }

    crc = c;

    bytes_out += (ulg)outcnt; 刷新總字節數

    outcnt = 0;

}

 

static void __init error(char *x) 解壓出錯調用的函數

{

        printk(KERN_ERR "%s", x);

        exit_code = 1;

}

 

static int __init

crd_load(struct file * fp, struct file *outfp)

{

        int result;

 

        insize = 0;                /* valid bytes in inbuf */

        inptr = 0;                /* index of next byte to be processed in inbuf */

        outcnt = 0;                /* bytes in output buffer */

        exit_code = 0;

        bytes_out = 0;

        crc = (ulg)0xffffffffL; /* shift register contents */

 

        crd_infp = fp;

        crd_outfp = outfp;

        inbuf = kmalloc(INBUFSIZ, GFP_KERNEL);

        if (inbuf == 0) {

                printk(KERN_ERR "RAMDISK: Couldn't allocate gzip buffer/n");

                return -1;

        }

        window = kmalloc(WSIZE, GFP_KERNEL);

        if (window == 0) {

                printk(KERN_ERR "RAMDISK: Couldn't allocate gzip window/n");

                kfree(inbuf);

                return -1;

        }

        makecrc();

        result = gunzip();

        kfree(inbuf);

        kfree(window);

        return result;

}

 

#endif  /* BUILD_CRAMDISK */

 

 

32位內核自解壓代碼

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

 

; arch/i386/boot/compressed/head.S

.text

 

#include ·

#include

 

        .globl startup_32        對於zImage該入口地址爲0x1000; 對於bzImage爲0x101000

 

startup_32:

        cld

        cli

        movl $(__KERNEL_DS),%eax

        movl %eax,%ds

        movl %eax,%es

        movl %eax,%fs

        movl %eax,%gs

 

        lss SYMBOL_NAME(stack_start),%esp        # 自解壓代碼的堆棧爲misc.c中定義的16K字節的數組

        xorl %eax,%eax

1:        incl %eax                # check that A20 really IS enabled

        movl %eax,0x000000        # loop forever if it isn't

        cmpl %eax,0x100000

        je 1b

 

/*

* Initialize eflags.  Some BIOS's leave bits like NT set.  This would

* confuse the debugger if this code is traced.

* XXX - best to initialize before switching to protected mode.

*/

        pushl $0

        popfl

/*

* Clear BSS        清除解壓程序的BSS段

*/

        xorl %eax,%eax

        movl $ SYMBOL_NAME(_edata),%edi

        movl $ SYMBOL_NAME(_end),%ecx

        subl %edi,%ecx

        cld

        rep

        stosb

/*

* Do the decompression, and jump to the new kernel..

*/

        subl $16,%esp        # place for structure on the stack

        movl %esp,%eax

        pushl %esi        # real mode pointer as second arg

        pushl %eax        # address of structure as first arg

        call SYMBOL_NAME(decompress_kernel)

        orl  %eax,%eax        # 若是返回非零,則表示爲內核解壓爲低端和高端的兩個片段

        jnz  3f

        popl %esi        # discard address

        popl %esi        # real mode pointer

        xorl %ebx,%ebx

        ljmp $(__KERNEL_CS), $0x100000        # 運行start_kernel

 

/*

* We come here, if we were loaded high.

* We need to move the move-in-place routine down to 0x1000

* and then start it with the buffer addresses in registers,

* which we got from the stack.

*/

3:

        movl $move_routine_start,%esi

        movl $0x1000,%edi

        movl $move_routine_end,%ecx

        subl %esi,%ecx

        addl $3,%ecx

        shrl $2,%ecx        # 按字取整

        cld

        rep

        movsl        # 將內核片段合併代碼複製到0x1000區域, 內核的片斷起始爲0x2000

 

        popl %esi        # discard the address

        popl %ebx        # real mode pointer

        popl %esi        # low_buffer_start  內核低端片斷的起始地址

        popl %ecx        # lcount                  內核低端片斷的字節數量

        popl %edx        # high_buffer_start 內核高端片斷的起始地址

        popl %eax        # hcount                  內核高端片斷的字節數量

        movl $0x100000,%edi                  內核合併的起始地址

        cli                # make sure we don't get interrupted

        ljmp $(__KERNEL_CS), $0x1000 # and jump to the move routine

 

/*

* Routine (template) for moving the decompressed kernel in place,

* if we were high loaded. This _must_ PIC-code !

*/

move_routine_start:

        movl %ecx,%ebp

        shrl $2,%ecx

        rep

        movsl                        # 按字拷貝第1個片斷

        movl %ebp,%ecx

        andl $3,%ecx

        rep

        movsb                        # 傳送不徹底字

        movl %edx,%esi

        movl %eax,%ecx        # NOTE: rep movsb won't move if %ecx == 0

        addl $3,%ecx

        shrl $2,%ecx        # 按字對齊

        rep

        movsl                        # 按字拷貝第2個片斷

        movl %ebx,%esi        # Restore setup pointer

        xorl %ebx,%ebx

        ljmp $(__KERNEL_CS), $0x100000        # 運行start_kernel

move_routine_end:

 

; arch/i386/boot/compressed/misc.c

 

/*

* gzip declarations

*/

 

#define OF(args)  args

#define STATIC static

 

#undef memset

#undef memcpy

#define memzero(s, n)     memset ((s), 0, (n))

 

typedef unsigned char  uch;

typedef unsigned short ush;

typedef unsigned long  ulg;

 

#define WSIZE 0x8000                /* Window size must be at least 32k, */

                                /* and a power of two */

 

static uch *inbuf;             /* input buffer */

static uch window[WSIZE];    /* Sliding window buffer */

 

static unsigned insize = 0;  /* valid bytes in inbuf */

static unsigned inptr = 0;   /* index of next byte to be processed in inbuf */

static unsigned outcnt = 0;  /* bytes in output buffer */

 

/* gzip flag byte */

#define ASCII_FLAG   0x01 /* bit 0 set: file probably ASCII text */

#define CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip file */

#define EXTRA_FIELD  0x04 /* bit 2 set: extra field present */

#define ORIG_NAME    0x08 /* bit 3 set: original file name present */

#define COMMENT      0x10 /* bit 4 set: file comment present */

#define ENCRYPTED    0x20 /* bit 5 set: file is encrypted */

#define RESERVED     0xC0 /* bit 6,7:   reserved */

 

#define get_byte()  (inptr

/* Diagnostic functions */

#ifdef DEBUG

#  define Assert(cond,msg) {if(!(cond)) error(msg);}

#  define Trace(x) fprintf x

#  define Tracev(x) {if (verbose) fprintf x ;}

#  define Tracevv(x) {if (verbose>1) fprintf x ;}

#  define Tracec(c,x) {if (verbose  (c)) fprintf x ;}

#  define Tracecv(c,x) {if (verbose>1  (c)) fprintf x ;}

#else

#  define Assert(cond,msg)

#  define Trace(x)

#  define Tracev(x)

#  define Tracevv(x)

#  define Tracec(c,x)

#  define Tracecv(c,x)

#endif

 

static int  fill_inbuf(void);

static void flush_window(void);

static void error(char *m);

static void gzip_mark(void **);

static void gzip_release(void **);

 

/*

* This is set up by the setup-routine at boot-time

*/

static unsigned char *real_mode; /* Pointer to real-mode data */

 

#define EXT_MEM_K   (*(unsigned short *)(real_mode + 0x2))

#ifndef STANDARD_MEMORY_BIOS_CALL

#define ALT_MEM_K   (*(unsigned long *)(real_mode + 0x1e0))

#endif

#define SCREEN_INFO (*(struct screen_info *)(real_mode+0))

 

extern char input_data[];

extern int input_len;

 

static long bytes_out = 0;

static uch *output_data;

static unsigned long output_ptr = 0;

 

 

static void *malloc(int size);

static void free(void *where);

static void error(char *m);

static void gzip_mark(void **);

static void gzip_release(void **);

 

static void puts(const char *);

 

extern int end;

static long free_mem_ptr = (long)

static long free_mem_end_ptr;

 

#define INPLACE_MOVE_ROUTINE  0x1000        內核片斷合併代碼的運行地址

#define LOW_BUFFER_START      0x2000        內核低端解壓片斷的起始地址

#define LOW_BUFFER_MAX       0x90000        內核低端解壓片斷的終止地址

#define HEAP_SIZE             0x3000        爲解壓低碼保留的堆的尺寸,堆起始於BSS的結束

static unsigned int low_buffer_end, low_buffer_size;

static int high_loaded =0;

static uch *high_buffer_start /* = (uch *)(((ulg) + HEAP_SIZE)*/;

 

static char *vidmem = (char *)0xb8000;

static int vidport;

static int lines, cols;

 

#include "../../../../lib/inflate.c"

 

static void *malloc(int size)

{

        void *p;

 

        if (size         if (free_mem_ptr

        free_mem_ptr = (free_mem_ptr + 3)  ~3;        /* Align */

 

        p = (void *)free_mem_ptr;

        free_mem_ptr += size;

 

        if (free_mem_ptr >= free_mem_end_ptr)

                error("/nOut of memory/n");

 

        return p;

}

 

static void free(void *where)

{        /* Don't care */

}

 

static void gzip_mark(void **ptr)

{

        *ptr = (void *) free_mem_ptr;

}

 

static void gzip_release(void **ptr)

{

        free_mem_ptr = (long) *ptr;

}

 

static void scroll(void)

{

        int i;

 

        memcpy ( vidmem, vidmem + cols * 2, ( lines - 1 ) * cols * 2 );

        for ( i = ( lines - 1 ) * cols * 2; i                 vidmem[ i ] = ' ';

}

 

static void puts(const char *s)

{

        int x,y,pos;

        char c;

 

        x = SCREEN_INFO.orig_x;

        y = SCREEN_INFO.orig_y;

 

        while ( ( c = *s++ ) != '/0' ) {

                if ( c == '/n' ) {

                        x = 0;

                        if ( ++y >= lines ) {

                                scroll();

                                y--;

                        }

                } else {

                        vidmem [ ( x + cols * y ) * 2 ] = c;

                        if ( ++x >= cols ) {

                                x = 0;

                                if ( ++y >= lines ) {

                                        scroll();

                                        y--;

                                }

                        }

                }

        }

 

        SCREEN_INFO.orig_x = x;

        SCREEN_INFO.orig_y = y;

 

        pos = (x + cols * y) * 2;        /* Update cursor position */

        outb_p(14, vidport);

        outb_p(0xff  (pos >> 9), vidport+1);

        outb_p(15, vidport);

        outb_p(0xff  (pos >> 1), vidport+1);

}

 

void* memset(void* s, int c, size_t n)

{

        int i;

        char *ss = (char*)s;

 

        for (i=0;i        return s;

}

 

void* memcpy(void* __dest, __const void* __src,

                            size_t __n)

{

        int i;

        char *d = (char *)__dest, *s = (char *)__src;

 

        for (i=0;i        return __dest;

}

 

/* ===========================================================================

* Fill the input buffer. This is called only when the buffer is empty

* and at least one byte is really needed.

*/

static int fill_inbuf(void)

{

        if (insize != 0) {

                error("ran out of input data/n");

        }

 

        inbuf = input_data;

        insize = input_len;

        inptr = 1;

        return inbuf[0];

}

 

/* ===========================================================================

* Write the output window window[0..outcnt-1] and update crc and bytes_out.

* (Used for the decompressed data only.)

*/

static void flush_window_low(void)

{

    ulg c = crc;         /* temporary variable */

    unsigned n;

    uch *in, *out, ch;

 

    in = window;

    out =

    for (n = 0; n             ch = *out++ = *in++;

            c = crc_32_tab[((int)c ^ ch)  0xff] ^ (c >> 8);

    }

    crc = c;

    bytes_out += (ulg)outcnt;

    output_ptr += (ulg)outcnt;

    outcnt = 0;

}

 

static void flush_window_high(void)

{

    ulg c = crc;         /* temporary variable */

    unsigned n;

    uch *in,  ch;

    in = window;

    for (n = 0; n         ch = *output_data++ = *in++;

        if ((ulg)output_data == low_buffer_end) output_data=high_buffer_start;

        c = crc_32_tab[((int)c ^ ch)  0xff] ^ (c >> 8);

    }

    crc = c;

    bytes_out += (ulg)outcnt;

    outcnt = 0;

}

 

static void flush_window(void)

{

        if (high_loaded) flush_window_high();

        else flush_window_low();

}

 

static void error(char *x)

{

        puts("/n/n");

        puts(x);

        puts("/n/n -- System halted");

 

        while(1);        /* Halt */

}

 

#define STACK_SIZE (4096)

 

long user_stack [STACK_SIZE];

 

struct {

        long * a;

        short b;

        } stack_start = {  user_stack [STACK_SIZE] , __KERNEL_DS };

 

void setup_normal_output_buffer(void) 對於zImage, 直接解壓到1M

{

#ifdef STANDARD_MEMORY_BIOS_CALL

        if (EXT_MEM_K #else

        if ((ALT_MEM_K > EXT_MEM_K ? ALT_MEM_K : EXT_MEM_K) #endif

        output_data = (char *)0x100000; /* Points to 1M */

        free_mem_end_ptr = (long)real_mode;

}

 

struct moveparams {

        uch *low_buffer_start;  int lcount;

        uch *high_buffer_start; int hcount;

};

 

void setup_output_buffer_if_we_run_high(struct moveparams *mv)

{

        high_buffer_start = (uch *)(((ulg) + HEAP_SIZE); 內核高端片斷的最小起始地址

#ifdef STANDARD_MEMORY_BIOS_CALL

        if (EXT_MEM_K #else

        if ((ALT_MEM_K > EXT_MEM_K ? ALT_MEM_K : EXT_MEM_K) #endif

        mv->low_buffer_start = output_data = (char *)LOW_BUFFER_START;

        low_buffer_end = ((unsigned int)real_mode > LOW_BUFFER_MAX

          ? LOW_BUFFER_MAX : (unsigned int)real_mode)  ~0xfff;

        low_buffer_size = low_buffer_end - LOW_BUFFER_START;

        high_loaded = 1;

        free_mem_end_ptr = (long)high_buffer_start;

        if ( (0x100000 + low_buffer_size) > ((ulg)high_buffer_start)) {

                ; 若是高端片斷的最小起始地址小於它實際應加載的地址,則將它置爲實際地址,

                ; 這樣高端片斷就無需再次移動了,不然它要向前移動

                high_buffer_start = (uch *)(0x100000 + low_buffer_size);

                mv->hcount = 0; /* say: we need not to move high_buffer */

        }

        else mv->hcount = -1; 待定

        mv->high_buffer_start = high_buffer_start;

}

 

void close_output_buffer_if_we_run_high(struct moveparams *mv)

{

        if (bytes_out > low_buffer_size) {

                mv->lcount = low_buffer_size;

                if (mv->hcount)

                        mv->hcount = bytes_out - low_buffer_size; 求出高端片斷的字節數

        } else { 若是解壓後內核只有低端的一個片斷

                mv->lcount = bytes_out;

                mv->hcount = 0;

        }

}

 

int decompress_kernel(struct moveparams *mv, void *rmode)

{

        real_mode = rmode;

 

        if (SCREEN_INFO.orig_video_mode == 7) {

                vidmem = (char *) 0xb0000;

                vidport = 0x3b4;

        } else {

                vidmem = (char *) 0xb8000;

                vidport = 0x3d4;

        }

 

        lines = SCREEN_INFO.orig_video_lines;

        cols = SCREEN_INFO.orig_video_cols;

 

        if (free_mem_ptr         else setup_output_buffer_if_we_run_high(mv);

 

        makecrc();

        puts("Uncompressing Linux... ");

        gunzip();

        puts("Ok, booting the kernel./n");

        if (high_loaded) close_output_buffer_if_we_run_high(mv);

        return high_loaded;

}

 

Edited by lucian_yao on 04/28/01 01:36 PM.

 

[目錄]


中斷

 

[目錄]


軟中斷


發信人: fist (星仔迷), 信區: SysInternals WWW-POST
標  題:  軟中斷
發信站: 武漢白雲黃鶴站 (Thu Mar 22 14:12:46 2001) , 轉信

軟中斷「一」 [張貼#: 88594 ]


1、 引言
軟中斷是linux系統原「底半處理」的升級,在原有的基礎上發展的新的處理方式,以適應多cpu 、多線程的軟中斷處理。
要了解軟中斷,咱們必需要先了原來底半處理的處理機制。
2、底半處理機制(基於2.0.3版本)
某些特殊時刻咱們並不肯意在覈心中執行一些操做。例如中斷處理過程當中。當中斷髮生時處理器將中止當前的工做, 操做系統將中斷髮送到相應的設備驅動上去。因爲此時系統中其餘程序都不能運行, 因此設備驅動中的中斷處理過程不宜過長。有些任務最好稍後執行。Linux底層部分處理機制能夠讓設備驅動和Linux核心其餘部分將這些工做進行排序以延遲執行。

系統中最多能夠有32個不一樣的底層處理過程;bh_base是指向這些過程入口的指針數組。而bh_active和 bh_mask用來表示那些處理過程已經安裝以及那些處於活動狀態。若是bh_mask的第N位置位則表示bh_base的 第N個元素包含底層部分處理例程。若是bh_active的第N位置位則表示第N個底層處理過程例程可在調度器認 爲合適的時刻調用。這些索引被定義成靜態的;定時器底層部分處理例程具備最高優先級(索引值爲0), 控制檯底層部分處理例程其次(索引值爲1)。典型的底層部分處理例程包含與之相連的任務鏈表。例如 immediate底層部分處理例程經過那些須要被馬上執行的任務的當即任務隊列(tq_immediate)來執行。
--引自David A Rusling的《linux核心》。

3、對2.4.1 軟中斷處理機制
下面,咱們進入軟中斷處理部份(softirq.c):

由softirq.c的代碼閱讀中,咱們能夠知道,在系統的初始化過程當中(softirq_init()),它使用了兩個數組:bh_task_vec[32],softirq_vec[32]。其中,bh_task_vec[32]填入了32個bh_action()的入口地址,但soft_vec[32]中,只有softirq_vec[0],和softirq_vec[3]分別填入了tasklet_action()和tasklet_hi_action()的地址。其他的保留它用。
當發生軟中斷時,系統並不急於處理,只是將相應的cpu的中斷狀態結構中的active 的相應的位置位,並將相應的處理函數掛到相應的隊列,而後等待調度時機來臨(如:schedule(),
系統調用返回異常時,硬中斷處理結束時等),系統調用do_softirq()來測試active位,再調用被激活的進程在這處過程當中,軟中斷的處理與底半處理有了差異,active 和mask再也不對應bh_base[nr], 而是對應softirq_vec[32]。在softirq.c中,咱們只涉及了softirq_vec[0]、softirq_vec[3]。這二者分別調用了tasklet_action()和tasklet_hi_action()來進行後續處理。這兩個過程比較類似,大體以下:
1, 鎖cpu的tasklet_vec[cpu]鏈表,取出鏈表,將原鏈表清空,解鎖,還給系統。
2, 對鏈表進行逐個處理。
3, 有沒法處理的,(task_trylock(t)失敗,可能有別的進程鎖定),插回系統鏈表。至此,系統完成了一次軟中斷的處理。

接下來有兩個問題:
1, bh_base[]依然存在,但應在何處調用?
2, tasklet_vec[cpu]隊列是什麼時候掛上的?


4、再探討
再次考查softirq.c 的bh_action()部份,發現有兩個判斷:
A:if(!spin_trylock(&global_bh_lock))goto:rescue
指明若是global_bh_lock 不能被鎖上(已被其它進程鎖上),則轉而執行rescue,將bh_base[nr]掛至tasklet_hi_vec[cpu]隊列中。等候中斷調度。
B:if(!hardirq_trylock(cpu)) goto tescue unlock 此時有硬中斷髮生,放入隊列推遲執行。若爲空閒,如今執行。

因而可知,這部分正是對應底半處理的程序,bh_base[]的延時處理正是底半處理的特色,能夠推測,若是沒有其它函數往tasklet_hi_vec[cpu]隊列掛入,那tasklet_hi_vec[cpu]正徹底對應着bh_base[]底半處理
在bh_action()中,把bh_ation()掛入tasklet_hi_vec[cpu]的正是mark_bh(),在整個源碼樹中查找,發現調用mark_bh()的函數不少,能夠理解,軟中斷產生之時,相關的函數會調用mark_bh(),將bh_action掛上tasklet_hi_vec隊列,而bh_action()的做用不過是在發現bh_base[nr]暫時沒法處理時重返隊列的方法。
由此可推測tasklet_vec隊列的掛接應與此類似,查看interrupt.h,找到tasklet_schedule()函數:
157 static inline void tasklet_schedule(struct tasklet_struct *t)
158 {
159 if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
160 int cpu = smp_processor_id();
161 unsigned long flags;
162
163 local_irq_save(flags);
164 t->next = tasklet_vec[cpu].list;
165 tasklet_vec[cpu].list = t; /*插入隊列。
166 __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
167 local_irq_restore(flags);
168 }
169 }
正是它爲tasklet_vec[cpu]隊列的創建立下了汗馬功勞,在源碼樹中,它亦被多個模塊調用,來完成它的使命。
至此,咱們能夠描繪一幅完整的軟中斷處理圖了。


如今,再來考查do_softirq()的softirq_vec[32],在interrupt.h中有以下定義:
56 enum
57 {
58 HI_SOFTIRQ=0,
59 NET_TX_SOFTIRQ,
60 NET_RX_SOFTIRQ,
61 TASKLET_SOFTIRQ
62 };

這四個變量應都是爲softirq_vec[]的下標,那麼,do_softirq()也將會處理NET_TX_SOFTIRQ和NET_RX_SOFTIRQ,是否還處理其它中斷,這有待探討。也許,這個do_softirq()有着極大的拓展性,等着咱們去開發呢。


我一直不明白,mask和active 是如何置位的。就個人理解,active 與softirq_vec[32]對應,而softirq_vec[]只對應中斷隊列的頭部,那麼,進行每次中斷響應時,往隊列中加入處理函數,是否都要對active置位?我在源碼中沒發現相應語句,盼指點。


我還等着老兄的下一篇呢!
我立刻寫點小補充。若是有什麼迷惑的地方,可得提哦!要否則,你們覺得你打算下回分解呢!:-)

主要經過__cpu_raise_softirq來設置
在hi_tasklet(也就是通常用於bh的)的處理裏面,在處理完當前的隊列後,會將補充的隊列從新掛上,而後標記(無論是否補充隊列裏面有tasklet):

local_irq_disable();
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_enable();

所以,對mark_bh根本不用設置這個active位。

對於通常的tasklet也同樣:
local_irq_disable();
t->next = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = t;
__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_enable();

其它的設置,能夠檢索上面的__cpu_raise_softirq

問題:在tasklet_action中,好象t後面還會被執行?

其實不以爲不妨不要考慮底半部分的影響,單獨把softirq, tasklet考慮一下,尤爲是在SMP的環境中,而後再不妨和原來的bh作個比較細緻的比較,這樣可能會理解得更深透些。:-)

 

謝謝lucian_yao兄指點,果真是__cpu_raise_softirq 在置位,是我疏忽了。但我認爲,mark_bh()在執行時調用了tasklet_hi_schedule對active置了位。對通常的tasklet也是經過調用tasklet_schedule()對active置了位,lucian_yao兄所列的示範語句,是tasklet_action將t插回後備隊列時,將active 重置位(不然就要由別的中斷來通知do_softirq(),這是不合理的)。
t 是在tasklet_trylock(t)失敗時交還給後備隊列的,交還後固然會再執行,但爲何會失敗,我也不瞭解。


你是對的。我疏忽了。在tasklet_schedule中果真有置位的語句。
在action語句中的確實是由於已經有softirq在執行,因此從新置位。


bottom half, softirq, tasklet, tqueue
[bottom half]
bh_base[32]
|
//
bh_action();
|
//
bh_task_vec[32];
| mark_bh(), tasklet_hi_schedule()
//
task_hi_action

bh_base對應的是32個函數,這些函數在bh_action()中調用
static void bh_action(unsigned long nr)
{
int cpu = smp_processor_id();

if (!spin_trylock(&global_bh_lock))
goto resched;

if (!hardirq_trylock(cpu))
goto resched_unlock;

if (bh_base[nr])
bh_base[nr]();

hardirq_endlock(cpu);
spin_unlock(&global_bh_lock);
return;

resched_unlock:
spin_unlock(&global_bh_lock);
resched:
mark_bh(nr);
}

在軟中斷初始化時,將bh_action()放到bh_task_vec[32]中,bh_task_vec[32]中元素的類型是tasklet_struct,系統使用mark_bh()或task_hi_schedule()函數將它掛到task_hi_vec[]的對列中,在系統調用do_softirq()時執行。
static inline void mark_bh(int nr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}

static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
int cpu = smp_processor_id();
unsigned long flags;

local_irq_save(flags);
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_restore(flags);
}
}

[softirq]
softirq_vec[32];
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
軟中斷對應一個softirq_action的結構,在do_softirq()中調用相應的action()作處理。
軟中斷初始化時只設置了0,3兩項,對應的action是task_hi_action和task_action.
1: task_hi_action
//
|
tasklet_hi_vec[NR_CPU]

struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;
struct tasklet_head
{
struct tasklet_struct *list;
} __attribute__ ((__aligned__(SMP_CACHE_BYTES)));

task_hi_action處理的對象是一個tasklet的隊列,每一個cpu都有一個對應的tasklet隊列,
它在tasklet_hi_schedule中動態添加。

3: task_action
//
|
tasklet_vec[NR_CPU]

[tasklet]
struct tasklet_struct
{

struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
從上面的分析來看tasklet只是一個調用實體,在do_softirq()中被調用。softirq的組織和結構纔是最重要的。

[目錄]


驅動中斷


發信人: fist (星仔迷), 信區: SysInternals WWW-POST
標  題: Linux設備驅動程序的中斷
發信站: 武漢白雲黃鶴站 (Mon Jul  2 20:51:38 2001) , 轉信

標題   Linux設備驅動程序的中斷
做者 coly (journeyman)
時間 07/02/01 11:24 AM

 

Linux設備驅動程序的中斷
Coly
V0.1
指定參考書:《Linux設備驅動程序》(初版)

這裏總結一下Linux設備驅動程序中涉及的中斷機制。

1、前言
Linux的中斷宏觀分爲兩種:軟中斷和硬中斷。聲明一下,這裏的軟和硬的意思是指和軟件

關以及和硬件相關,而不是軟件實現的中斷或硬件實現的中斷。
軟中斷就是「信號機制」。軟中不是軟件中斷。Linux經過信號來產生對進程的各類中斷操

,咱們如今知道的信號共有31個,其具體內容這裏略過,感興趣讀者可參看相關參考文獻[1
]。通常來講,軟中斷是由內核機制的觸發事件引發的(例如進程運行超時),可是不可忽

有大量的軟中斷也是因爲和硬件有關的中斷引發的,例如當打印機端口產生一個硬件中斷時
,會通知和硬件相關的硬中斷,硬中斷就會產生一個軟中斷並送到操做系統內核裏,這樣內
核就會根據這個軟中斷喚醒睡眠在打印機任務隊列中的處理進程。
硬中斷就是一般意義上的「中斷處理程序」,它是直接處理由硬件發過來的中斷信號的。當
硬中斷收到它應當處理的中斷信號之後,就回去本身驅動的設備上去看看設備的狀態寄存器
以瞭解發生了什麼事情,並進行相應的操做。
對於軟中斷,咱們不作討論,那是進程調度裏要考慮的事情。因爲咱們討論的是設備驅動程
序的中斷問題,因此焦點集中在硬中斷裏。咱們這裏討論的是硬中斷,即和硬件相關的中斷

2、中斷產生
要中斷,是由於外設須要通知操做系統她那裏發生了一些事情,可是中斷的功能僅僅是一個
設備報警燈,當燈亮的時候中斷處理程序只知道有事情發生了,但發生了什麼事情還要親自
到設備那裏去看才行。也就是說,當中斷處理程序得知設備發生了一箇中斷的時候,它並不
知道設備發生了什麼事情,只有當它訪問了設備上的一些狀態寄存器之後,才能知道具體發
生了什麼,要怎麼去處理。
設備經過中斷線向中斷控制器發送高電平告訴操做系統它產生了一箇中斷,而操做系統會從
中斷控制器的狀態位知道是哪條中斷線上產生了中斷。PC機上使用的中斷控制器是8259,這
種控制器每個能夠管理8條中斷線,當兩個8259級聯的時候共能夠控制15條中斷線。這裏

中斷線是實實在在的電路,他們經過硬件接口鏈接到CPU外的設備控制器上。

3、IRQ
並非每一個設備均可以向中斷線上發中斷信號的,只有對某一條肯定的中斷線勇有了控制權
,才能夠向這條中斷線上發送信號。因爲計算機的外部設備愈來愈多,因此15條中斷線已經
不夠用了,中斷線是很是寶貴的資源。要使用中斷線,就得進行中斷線的申請,就是IRQ(In
terrupt Requirement),咱們也常把申請一條中斷線成爲申請一個IRQ或者是申請一箇中斷


IRQ是很是寶貴的,因此咱們建議只有當設備須要中斷的時候才申請佔用一個IRQ,或者是在
申請IRQ時採用共享中斷的方式,這樣能夠讓更多的設備使用中斷。
不管對IRQ的使用方式是獨佔仍是共享,申請IRQ的過程都是同樣的,分爲3步:
1.將全部的中斷線探測一遍,看看哪些中斷尚未被佔用。從這些尚未被佔用的中斷中

一個做爲該設備的IRQ。
2.經過中斷申請函數申請選定的IRQ,這是要指定申請的方式是獨佔仍是共享。
3.根據中斷申請函數的返回值決定怎麼作:若是成功了萬事大吉,若是沒成功則或者從新

請或者放棄申請並返回錯誤。
申請IRQ的過程,在參考書的配的源代碼裏有詳細的描述,讀者能夠經過仔細閱讀源代碼中

short一例對中斷號申請由深入的理解。

4、中斷處理程序
Linux中的中斷處理程序頗有特點,它的一箇中斷處理程序分爲兩個部分:上半部(top hal
f)和下半部(bottom half)。之因此會有上半部和下半部之分,徹底是考慮到中斷處理的效
率。
上半部的功能是「登記中斷」。當一箇中斷髮生時,他就把設備驅動程序中中斷例程的下半
部掛到該設備的下半部執行隊列中去,而後就沒事情了--等待新的中斷的到來。這樣一來
,上半部執行的速度就會很快,他就能夠接受更多她負責的設備產生的中斷了。上半部之所
以要快,是由於它是徹底屏蔽中斷的,若是她不執行完,其它的中斷就不能被及時的處理,
只能等到這個中斷處理程序執行完畢之後。因此,要儘量多得對設備產生的中斷進行服務
和處理,中斷處理程序就必定要快。
可是,有些中斷事件的處理是比較複雜的,因此中斷處理程序必須多花一點時間纔可以把事
情作完。可怎麼樣化解在短期內完成複雜處理的矛盾呢,這時候Linux引入了下半部的概

。下半部和上半部最大的不一樣是下半部是可中斷的,而上半部是不可中斷的。
下半部幾乎作了中斷處理程序全部的事情,由於上半部只是將下半部排到了他們所負責的設
備的中斷處理隊列中去,而後就什麼都無論了。下半部通常所負責的工做是察看設備以得到
產生中斷的事件信息,並根據這些信息(通常經過讀設備上的寄存器得來)進行相應的處理
。若是有些時間下半部不知道怎麼去作,他就使用著名的鴕鳥算法來解決問題--說白了就
是忽略這個事件。
因爲下半部是可中斷的,因此在它運行期間,若是其它的設備產生了中斷,這個下半部能夠
暫時的中斷掉,等到那個設備的上半部運行完了,再回頭來運行它。可是有一點必定要注意
,那就是若是一個設備中斷處理程序正在運行,不管她是運行上半部仍是運行下半部,只要
中斷處理程序尚未處理完畢,在這期間設備產生的新的中斷都將被忽略掉。由於中斷處理
程序是不可重入的,同一個中斷處理程序是不能並行的。
在Linux Kernel 2.0之前,中斷分爲快中斷和慢中斷(僞中斷咱們這裏不談),其中快中斷
的下半部也是不可中斷的,這樣能夠保證它執行的快一點。可是因爲如今硬件水平不斷上升
,快中斷和慢中斷的運行速度已經沒有什麼差異了,因此爲了提升中斷例程事務處理的效率
,從Linux kernel 2.0之後,中斷處理程序所有都是慢中斷的形式了--他們的下半部是可
以被中斷的。
可是,在下半部中,你也能夠進行中斷屏蔽--若是某一段代碼不能被中斷的話。你能夠使
用cti、sti或者是save_flag、restore_flag來實現你的想法。至於他們的用法和區別,請

看本文指定參考書中斷處理部分。
進一步的細節請讀者參看本文指定參考書,這裏就再也不所說了,詳細介紹細節不是個人目的
,個人目的是整理概念。

5、置中斷標誌位
在處理中斷的時候,中斷控制器會屏蔽掉原先發送中斷的那個設備,直到她發送的上一個中
斷被處理完了爲止。所以若是發送中斷的那個設備載中斷處理期間又發送了一箇中斷,那麼
這個中斷就被永遠的丟失了。
之因此發生這種事情,是由於中斷控制器並不能緩衝中斷信息,因此當前一箇中斷沒有處理
完之前又有新的中斷到達,他確定會丟掉新的中斷的。可是這種缺陷能夠經過設置主處理器
(CPU)上的「置中斷標誌位」(sti)來解決,由於主處理器具備緩衝中斷的功能。若是使用
了「置中斷標誌位」,那麼在處理完中斷之後使用sti函數就能夠使先前被屏蔽的中斷獲得

務。

6、中斷處理程序的不可重入性
上一節中咱們提到有時候須要屏蔽中斷,但是爲何要將這個中斷屏蔽掉呢?這並非由於
技術上實現不了同一中斷例程的並行,而是出於管理上的考慮。之因此在中斷處理的過程當中
要屏蔽同一IRQ來的新中斷,是由於中斷處理程序是不可重入的,因此不能並行執行同一個

斷處理程序。在這裏咱們舉一個例子,從這裏子例中能夠看出若是一箇中斷處理程序是能夠
並行的話,那麼頗有可能會發生驅動程序鎖死的狀況。當驅動程序鎖死的時候,你的操做系
統並不必定會崩潰,可是鎖死的驅動程序所支持的那個設備是不能再使用了--設備驅動程
序死了,設備也就死了。

 

 

 

 

 

 


上圖是一箇中斷處理程序的下半部並行時的狀況,其中A是一段代碼,B是操做設備寄存器R1
的代碼,C是操做設備寄存器R2的代碼。
其中激發PS1的事件會使A1產生一箇中斷,而後B1去讀R1中已有的數據,而後代碼C1向R2中

數據。而激發PS2的事件會使A2產生一箇中斷,而後B2刪除R1中的數據,而後C2讀去R2中的

據。
若是PS1先產生,且當他執行到A1和B1之間的時候,若是PS2產生了,這是A2會產生一箇中斷
,將PS2中斷掉(掛到任務隊列的尾部),而後刪除了R1的內容。當PS2運行到C2時,因爲C1
尚未向R2中寫數據,因此C2將會在這裏被掛起,PS2就睡眠在代碼C2上,直到有數據可讀

時候被信號喚醒。這是因爲PS1中的B2原先要讀的R1中的數據被PS2中的B2刪除了,因此PS1

會睡眠在B1上,直到有數據可讀的時候被信號喚醒。這樣一來,喚醒PS1和PS2的事件就永遠
不會發生了,所以PS1和PS2之間就鎖死了。
因爲設備驅動程序要和設備的寄存器打交道,因此很難寫出能夠重入的代碼來,由於設備寄
存器就是全局變量。所以,最簡潔的辦法就是禁止同一設備的中斷處理程序並行,即設備的
中斷處理程序是不可重入的。
有一點必定要清楚:在2.0版本之後的Linux kernel中,全部的上半部都是不可中斷的(上

部的操做是原子性的);不一樣設備的下半部能夠互相中斷,但一個特定的下半部不能被它自
己所中斷(即同一個下半部不能並行)。
因爲中斷處理程序要求不可重入,因此程序員也沒必要爲編寫可重入的代碼而頭痛了。以個人
經驗,編寫可重入的設備驅動程序是能夠的,編寫可重入的中斷處理程序是很是可貴,幾乎
不可能。

7、避免競爭條件的出現
咱們都知道,一旦競爭條件出現了,就有可能會發生死鎖的狀況,嚴重時可能會將整個系統
鎖死。因此必定要避免競爭條件的出現。
這裏我很少說,你們只要注意一點:絕大多數因爲中斷產生的競爭條件,都是在帶有中斷的
內核進程被睡眠形成的。因此在實現中斷的時候,必定要相信謹慎的讓進程睡眠,必要的時
候能夠使用cli、sti或者save_flag、restore_flag。具體細節請參看本文指定參考書。

8、實現
如何實現驅動程序的中斷例程,是各位讀者的事情了。只要大家仔細的閱讀short例程的源

碼,搞清楚編寫驅動程序中斷例程的規則,就能夠編寫本身的中斷例程了。只要概念正確,

在正確的規則下編寫你的代碼,那就是符合道理的東西。我始終強調,概念是第一位的,能
編多少代碼是很其次的,咱們必定要概念正確,才能進行正確的思考。

9、小結
本文介紹了Linux驅動程序中的中斷,若是讀者已經新癢了的話,那麼打開機器開始動手吧

Time for you to leave!
參考文獻:
1.Linux網絡編程
2.編程之道
3.Linux設備驅動程序
4.Mouse drivers
5.Linux Kernel Hacking Guide
6.Unreliable Guide To Hacking The Linux Kernel

 


--

 

[目錄]


硬件中斷 from aka


硬件中斷

硬件中斷概述

中斷能夠用下面的流程來表示:


中斷產生源 --> 中斷向量表 (idt) --> 中斷入口 ( 通常簡單處理後調用相應的函數) --->do_IRQ--> 後續處理(軟中斷等工做)


如圖:

具體地說,處理過程以下:

 

中斷信號由外部設備發送到中斷芯片(模塊)的引腳


中斷芯片將引腳的信號轉換成數字信號傳給CPU,例如8259主芯片引腳0發送的是0x20


CPU接收中斷後,到中斷向量表IDT中找中斷向量


根據存在中斷向量中的數值找到向量入口


由向量入口跳轉到一個統一的處理函數do_IRQ


在do_IRQ中可能會標註一些軟中斷,在執行完do_IRQ後執行這些軟中斷。


下面一一介紹。

 

8259芯片

本文主要參考周明德《微型計算機系統原理及應用》和billpan的相關帖子

 

1.中斷產生過程

(1)若是IR引腳上有信號,會使中斷請求寄存器(Interrupt Request Register,IRR)相應的位置位,好比圖中, IR3, IR4, IR5上有信號,那麼IRR的3,4,5爲1


(2)若是這些IRR中有一個是容許的,也就是沒有被屏蔽,那麼就會經過INT向CPU發出中斷請求信號。屏蔽是由中斷屏蔽寄存器(Interrupt Mask Register,IMR)來控制的,好比圖中位3被置1,也就是IRR位3的信號被屏蔽了。在圖中,還有4,5的信號沒有被屏蔽,因此,會向CPU發出請求信號。


(3)若是CPU處於開中斷狀態,那麼在執行指令的最後一個週期,在INTA上作出迴應,而且關中斷.


(4)8259A收到迴應後,將中斷服務寄存器(In-Service Register)置位,而將相應的IRR復位:

8259芯片會比較IRR中的中斷的優先級,如上圖中,因爲IMR中位3處於屏蔽狀態,因此實際上只是比較IR4,I5,缺省狀況下,IR0最高,依次往下,IR7最低(這種優先級能夠被設置),因此上圖中,ISR被設置爲4.


(5)在CPU發出下一個INTA信號時,8259將中斷號送到數據線上,從而能被CPU接收到,這裏有個問題:好比在上圖中,8259得到的是數4,可是CPU須要的是中斷號(並不爲4),從而能夠到idt找相應的向量。因此有一個從ISR的信號到中斷號的轉換。在Linux的設置中,4對應的中斷號是0x24.


(6)若是8259處於自動結束中斷(Automatic End of Interrupt AEOI)狀態,那麼在剛纔那個INTA信號結束前,8259的ISR復位(也就是清0),若是不處於這個狀態,那麼直到CPU發出EOI指令,它纔會使得ISR復位。

 

2.一些相關專題


(1)從8259


在x86單CPU的機器上採用兩個8259芯片,主芯片如上圖所示,x86模式規定,從8259將它的INT腳與主8259的IR2相連,這樣,若是從8259芯片的引腳IR8-IR15上有中斷,那麼會在INT上產生信號,主8259在IR2上產生了一個硬件信號,當它如上面的步驟處理後將IR2的中斷傳送給CPU,收到應答後,會經過CAS通知從8259芯片,從8259芯片將IRQ中斷號送到數據線上,從而被CPU接收。

由此,我猜想它產生的全部中斷在主8259上優先級爲2,不知道對不對。


(2)關於屏蔽


從上面能夠看出,屏蔽有兩種方法,一種做用於CPU, 經過清除IF標記,使得CPU不去響應8259在INT上的請求。也就是所謂關中斷。

另外一種方法是,做用於8259,經過給它指令設置IMR,使得相應的IRR不參與ISR(見上面的(4)),被稱爲禁止(disable),反之,被稱爲容許(enable).

每次設置IMR只須要對端口0x21(主)或0xA1(從)輸出一個字節便可,字節每位對應於IMR每位,例如:


outb(cached_21,0x21);


爲了統一處理16箇中斷,Linux用一個16位cached_irq_mask變量來記錄這16箇中斷的屏蔽狀況:


static unsigned int cached_irq_mask = 0xffff;


爲了分別對應於主從芯片的8位IMR,將這16位cached_irq_mask分紅兩個8位的變量:


#define __byte(x,y) (((unsigned char *)&(y))[x])

#define cached_21 (__byte(0,cached_irq_mask))

#define cached_A1 (__byte(1,cached_irq_mask))


在禁用某個irq的時候,調用下面的函數:


void disable_8259A_irq(unsigned int irq){

unsigned int mask = 1 << irq; unsigned long flags; spin_lock_irqsave(&i8259A_lock, flags);

cached_irq_mask |= mask;/*-- 對這16位變量設置 */


if (irq & 8)/*-- 看是對主8259設置仍是對從芯片設置 */ outb(cached_A1,0xA1);/*-- 對從8259芯片設置 */

else

outb(cached_21,0x21);/*-- 對主8259芯片設置 */ spin_unlock_irqrestore(&i8259A_lock, flags);

}


(3)關於中斷號的輸出


8259在ISR裏保存的只是irq的ID,可是它告訴CPU的是中斷向量ID,好比ISR保存時鐘中斷的ID 0,可是在通知CPU倒是中斷號0x20.所以須要創建一個映射。在8259芯片產生的IRQ號必須是連續的,也就是若是irq0對應的是中斷向量0x20,那麼irq1對應的就是0x21,...


在i8259.c/init_8259A()中,進行設置:


outb_p(0x11, 0x20); /* ICW1: select 8259A-1 init */

outb_p(0x20 + 0, 0x21); /* ICW2: 8259A-1 IR0-7 mapped to 0x20-0x27 */

outb_p(0x04, 0x21); /* 8259A-1 (the master) has a slave on IR2 */

if (auto_eoi)

outb_p(0x03, 0x21); /* master does Auto EOI */

else

outb_p(0x01, 0x21); /* master expects normal EOI */


outb_p(0x11, 0xA0); /* ICW1: select 8259A-2 init */

outb_p(0x20 + 8, 0xA1); /* ICW2: 8259A-2 IR0-7 mapped to 0x28-0x2f */

outb_p(0x02, 0xA1); /* 8259A-2 is a slave on master's IR2 */

outb_p(0x01, 0xA1); /* (slave's support for AEOI in flat mode is to be investigated) */


這樣,在IDT的向量0x20-0x2f能夠分別填入相應的中斷處理函數的地址了。

向量表設置


注:圖片均取自i386開發者手冊.


i386中斷門描述符

描述符如圖:

段選擇符和偏移量決定了中斷處理函數的入口地址,以下圖。

 

在這裏段選擇符指向內核中惟一的一個代碼段描述符的地址__KERNEL_CS(=0x10),而這個描述符定義的段爲0到4G:

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

ENTRY(gdt_table) .quad 0x0000000000000000 /* NULL descriptor */

.quad 0x0000000000000000 /* not used */

.quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */

... ...

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

而偏移量就成了絕對的偏移量了,在IDT的描述符中被拆成了兩部分,分別放在頭和尾。


P標誌着這個代碼段是否在內存中,原本是i386提供的相似缺頁的機制,在Linux中這個已經不用了,都設成1(固然內核代碼是永駐內存的,但即便不在內存,推測linux也只會用缺頁的標誌)。

DPL在這裏是0級(特權級)

0D110中,D爲1,代表是32位程序(這個細節見i386開發手冊).110是中斷門的標識,其它101是任務門的標識, 111是陷阱(trap)門標識。

 

Linux對中斷門的設置


因而在Linux中對硬件中斷的中斷門的設置爲:


init_IRQ(void)

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

for (i = 0; i < NR_IRQS; i++) {

int vector = FIRST_EXTERNAL_VECTOR + i;

if (vector != SYSCALL_VECTOR)

set_intr_gate(vector, interrupt[ i]);

}

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


其中,FIRST_EXTERNAL_VECTOR=0x20,剛好爲8259芯片的IR0的中斷門(見8259部分),也就是時鐘中斷的中斷門),interrupt[ i]爲相應處理函數的入口地址

NR_IRQS=224, =256(IDT的向量總數)-32(CPU保留的中斷的個數),在這裏設置了全部可設置的向量。

SYSCALL_VECTOR=0x80,在這裏意思是避開系統調用這個向量。


而set_intr_gate的定義是這樣的:

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

void set_intr_gate(unsigned int n, void *addr){

_set_gate(idt_table+n,14,0,addr);

}

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

其中,須要解釋的是:14是標識指明這個是中斷門,注意上面的0D110=01110=14;另外,0指明的是DPL.

中斷入口


以8259的16箇中斷爲例:

經過宏BUILD_16_IRQS(0x0), BI(x,y),以及


#define BUILD_IRQ(nr) /

asmlinkage void IRQ_NAME(nr); /

__asm__( /

"/n"__ALIGN_STR"/n" /

SYMBOL_NAME_STR(IRQ) #nr "_interrupt:/n/t" /

"pushl $"#nr"-256/n/t" /

"jmp common_interrupt");


獲得的16箇中斷處理函數爲:


IRQ0x00_interrupt:

push $0x00 - 256

jump common_interrupt


IRQ0x00_interrupt:

push $0x01 - 256

jump common_interrupt


... ...


IRQ0x0f_interrupt:

push $0x0f - 256

jump common_interrupt


這些處理函數簡單的把中斷號-256(爲何-256,也許是避免和內部中斷的中斷號有衝突)壓到棧中,而後跳到common_interrupt


其中common_interrupt是由宏BUILD_COMMON_IRQ()展開:

 


#define BUILD_COMMON_IRQ() /

asmlinkage void call_do_IRQ(void); /

__asm__( /

"/n" __ALIGN_STR"/n" /

"common_interrupt:/n/t" /

SAVE_ALL /

"pushl $ret_from_intr/n/t" /

SYMBOL_NAME_STR(call_do_IRQ)":/n/t" /

"jmp "SYMBOL_NAME_STR(do_IRQ));


.align 4,0x90common_interrupt:

SAVE_ALL展開的保護現場部分

push $ret_from_intrcall

do_IRQ:

jump do_IRQ;


從上面能夠看出,這16個的中斷處理函數不過是把中斷號-256壓入棧中,而後保護現場,最後調用do_IRQ .在common_interrupt中,爲了使do_IRQ返回到entry.S的ret_from_intr標號,因此採用的是壓入返回點ret_from_intr,用jump來模擬一個從ret_from_intr上面對do_IRQ的一個調用。

 


和IDT的銜接


爲了便於IDT的設置,在數組interrupt中填入全部中斷處理函數的地址:


void (*interrupt[NR_IRQS])(void) = {

IRQ0x00_interrupt,

IRQ0x01_interrupt,

... ...

}

在中斷門的設置中,能夠看到是如何利用這個數組的。

硬件中斷處理函數do_IRQ

do_IRQ的相關對象


在do_IRQ中,一箇中斷主要由三個對象來完成,如圖:

 


其中, irq_desc_t對象構成的irq_desc[]數組元素分別對應了224個硬件中斷(idt一共256項,cpu本身前保留了32項,256-32=224,固然這裏面有些項是不用的,好比x80是系統調用).


當發生中斷時,函數do_IRQ就會在irq_desc[]相應的項中提取各類信息來完成對中斷的處理。


irq_desc有一個字段handler指向發出這個中斷的設備的處理對象hw_irq_controller,好比在單CPU,這個對象通常就是處理芯片8259的對象。爲何要指向這個對象呢?由於當發生中斷的時候,內核須要對相應的中斷進行一些處理,好比屏蔽這個中斷等。這個時候須要對中斷設備(好比8259芯片)進行操做,因而能夠經過這個指針指向的對象進行操做。


irq_desc還有一個字段action指向對象irqaction,後者是產生中斷的設備的處理對象,其中的handler就是處理函數。因爲一箇中斷能夠由多個設備發出,Linux內核採用輪詢的方式,將全部產生這個中斷的設備的處理對象連成一個鏈表,一個一個執行。


例如,硬盤1,硬盤2都產生中斷IRQx,在do_IRQ中首先找到irq_desc[x],經過字段handler對產生中斷IRQx的設備進行處理(對8259而言,就是屏蔽之後的中斷IRQx),而後經過action前後運行硬盤1和硬盤2的處理函數。

 

hw_irq_controller

hw_irq_controller有多種:


1.在通常單cpu的機器上,一般採用兩個8259芯片,所以hw_irq_controller指的就是i8259A_irq_type


2.在多CPU的機器上,採用APIC子系統來處理芯片,APIC有3個部分組成,一個是I/O APIC模塊,其做用可比作8259芯片,可是它發出的中斷信號會經過 APIC總線送到其中一個(或幾個)CPU中的Local APIC模塊,所以,它還起一個路由的做用;它能夠接收16箇中斷。


中斷能夠採起兩種方式,電平觸發和邊沿觸發,相應的,I/O APIC模塊的hw_irq_controller就有兩種:


ioapic_level_irq_type

ioapic_edge_irq_type


(這裏指的是intel的APIC,還有其它公司研製的APIC,我沒有研究過)


3. Local APIC本身也能單獨處理一些直接對CPU產生的中斷,例如時鐘中斷(這和沒有使用Local APIC模塊的CPU不一樣,它們接收的時鐘中斷來自外圍的時鐘芯片),所以,它也有本身的 hw_irq_controller:

lapic_irq_type


struct hw_interrupt_type {

const char * typename;

unsigned int (*startup)(unsigned int irq);

void (*shutdown)(unsigned int irq);

void (*enable)(unsigned int irq);

void (*disable)(unsigned int irq);

void (*ack)(unsigned int irq);

void (*end)(unsigned int irq);

void (*set_affinity)(unsigned int irq, unsigned long mask);

};


typedef struct hw_interrupt_type hw_irq_controller;

 


startup 是啓動中斷芯片(模塊),使得它開始接收中斷,通常狀況下,就是將 全部被屏蔽的引腳取消屏蔽

shutdown 反之,使得芯片再也不接收中斷

enable 設某個引腳能夠接收中斷,也就是取消屏蔽

disable 屏蔽某個引腳,例如,若是屏蔽0那麼時鐘中斷就再也不發生

ack 當CPU收到來自中斷芯片的中斷信號,給相應的引腳的處理,這個各類狀況下 (8259, APIC電平,邊沿)的處理都不相同

end 在CPU處理完某個引腳產生的中斷後,對中斷芯片(模塊)的操做。

irqaction

將一個硬件處理函數掛到相應的處理隊列上去(固然首先要生成一個irqaction結構):


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

int request_irq(unsigned int irq,

void (*handler)(int, void *, struct pt_regs *),

unsigned long irqflags,

const char * devname,

void *dev_id)

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


參數說明在源文件裏說得很是清楚。

handler是硬件處理函數,在下面的代碼中能夠看得很清楚:


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

do {

status |= action->flags;

action->handler(irq, action->dev_id, regs);

action = action->next;

} while (action);

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


第二個參數就是action的dev_id,這個參數很是靈活,能夠派各類用處。並且要保證的是,這個dev_id在這個處理鏈中是惟一的,不然刪除會遇到麻煩。

第三個參數是在entry.S中壓入的各個積存器的值。


它的大體流程是:


1.在slab中分配一個irqaction,填上必需的數據

如下在函數setup_irq中。

2.找到它的irq對應的結構irq_desc

3.看它是否想對隨機數作貢獻

4.看這個結構上是否已經掛了其它處理函數了,若是有,則必須確保它自己和這個隊列上全部的處理函數都是可共享的(因爲傳遞性,只需判斷一個就能夠了)

5.掛到隊列最後

6.若是這個irq_desc只有它一個irqaction,那麼還要進行一些初始化工做

7在proc/下面登記 register_irq_proc(irq)(這個我不太明白)


將一個處理函數取下:


void free_irq(unsigned int irq, void *dev_id)


首先在隊列裏找到這個處理函數(嚴格的說是irqaction),主要靠dev_id來匹配,這時dev_id的惟一性就比較重要了。


將它從隊列裏剔除。

若是這個中斷號沒有處理函數了,那麼禁止這個中斷號上再產生中斷:


if (!desc->action) {

desc->status |= IRQ_DISABLED;

desc->handler->shutdown(irq);

}


若是其它CPU在運行這個處理函數,要等到它運行完了,才釋放它:


#ifdef CONFIG_SMP


/* Wait to make sure it's not being used on another CPU */

while (desc->status & IRQ_INPROGRESS)

barrier();

#endif

kfree(action);

 

do_IRQ

asmlinkage unsigned int do_IRQ(struct pt_regs regs)


1.首先取中斷號,而且獲取對應的irq_desc:


int irq = regs.orig_eax & 0xff; /* high bits used in ret_from_ code */

int cpu = smp_processor_id();

irq_desc_t *desc = irq_desc + irq;


2.對中斷芯片(模塊)應答:


desc->handler->ack(irq);


3.修改它的狀態(注:這些狀態我以爲只有在SMP下才有意義):


status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);

status |= IRQ_PENDING; /* we _want_ to handle it */


IRQ_REPLAY是指若是被禁止的中斷號上又產生了中斷,這個中斷是不會被處理的,當這個中斷號被容許產生中斷時,會將這個未被處理的中斷轉爲IRQ_REPLAY。


IRQ_WAITING 探測用,探測時,會將全部沒有掛處理函數的中斷號上設置IRQ_WAITING,若是這個中斷號上有中斷產生,就把這個狀態去掉,所以,咱們就能夠知道哪些中斷引腳上產生過中斷了。


IRQ_PENDING , IRQ_INPROGRESS是爲了確保:

 

同一個中斷號的處理程序不能重入


不能丟失這個中斷號的下一個處理程序


具體的說,當內核在運行某個中斷號對應的處理程序(鏈)時,狀態會設置成IRQ_INPROGRESS。若是在這期間,同一個中斷號上又產生了中斷,而且傳給CPU,那麼當內核打算再次運行這個中斷號對應的處理程序(鏈)時,發現已經有一個實例在運行了,就將這下一個中斷標註爲IRQ_PENDING, 而後返回。這個已在運行的實例結束的時候,會查看是否期間有同一中斷髮生了,是則再次執行一遍。


這些狀態的操做不是在什麼狀況下都必須的,事實上,一個CPU,用8259芯片,不管即便是開中斷,也不會發生中斷重入的狀況,由於在這期間,內核把同一中斷屏蔽掉了。


多個CPU比較複雜,由於CPU由Local APIC,每一個都有本身的中斷,可是它們可能調用同一個函數,好比時鐘中斷,每一個CPU均可能產生,它們都會調用時鐘中斷處理函數。


從I/O APIC傳過來的中斷,若是是電平觸發,也不會,由於在結束髮出EOI前,這個引腳上是不接收中斷信號。若是是邊沿觸發,要麼是開中斷,要麼I/O APIC選擇不一樣的CPU,在這兩種狀況下,會有重入的可能。


/*

* If the IRQ is disabled for whatever reason, we cannot

* use the action we have.

*/

action = NULL;

if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {

action = desc->action;

status &= ~IRQ_PENDING; /* we commit to handling */

status |= IRQ_INPROGRESS; /* we are handling it *//*進入執行狀態*/

}

desc->status = status;


/*

* If there is no IRQ handler or it was disabled, exit early.

Since we set PENDING, if another processor is handling

a different instance of this same irq, the other processor

will take care of it.

*/

if (!action)

goto out;/*要麼該中斷沒有處理函數;要麼被禁止運行(IRQ_DISABLE);要麼有一個實例已經在運行了*/


/*

* Edge triggered interrupts need to remember

* pending events.

* This applies to any hw interrupts that allow a second

* instance of the same irq to arrive while we are in do_IRQ

* or in the handler. But the code here only handles the _second_

* instance of the irq, not the third or fourth. So it is mostly

* useful for irq hardware that does not mask cleanly in an

* SMP environment.

*/

for (;;) {

spin_unlock(&desc->lock);

handle_IRQ_event(irq, &regs, action);/*執行函數鏈*/

spin_lock(&desc->lock);


if (!(desc->status & IRQ_PENDING))/*發現期間有中斷,就再次執行*/

break;

desc->status &= ~IRQ_PENDING;

}

desc->status &= ~IRQ_INPROGRESS;/*退出執行狀態*/

out:

/*

* The ->end() handler has to deal with interrupts which got

* disabled while the handler was running.

*/

desc->handler->end(irq);/*給中斷芯片一個結束的操做,通常是容許再次接收中斷*/

spin_unlock(&desc->lock);


if (softirq_active(cpu) & softirq_mask(cpu))

do_softirq();/*執行軟中斷*/

return 1;

}

軟中斷softirq

softirq簡介

提出softirq的機制的目的和老版本的底半部分的目的是一致的,都是將某個中斷處理的一部分任務延遲到後面去執行。


Linux內核中一共能夠有32個softirq,每一個softirq實際上就是指向一個函數。當內核執行softirq(do_softirq),就對這32個softirq進行輪詢:


(1)是否該softirq被定義了,而且容許被執行?

(2)是否激活了(也就是之前有中斷要求它執行)?


若是獲得確定的答覆,那麼就執行這個softirq指向的函數。


值得一提的是,不管有多少個CPU,內核一共只有32個公共的softirq,可是每一個CPU能夠執行不一樣的softirq,能夠禁止/起用不一樣的softirq,能夠激活不一樣的softirq,所以,能夠說,全部CPU有相同的例程,可是

每一個CPU卻有本身徹底獨立的實例。


對(1)的判斷是經過考察irq_stat[ cpu ].mask相應的位獲得的。這裏面的cpu指的是當前指令所在的cpu.在一開始,softirq被定義時,全部的cpu的掩碼mask都是同樣的。可是在實際運行中,每一個cpu上運行的程序能夠根據本身的須要調整。

對(2)的判斷是經過考察irq_stat[ cpu ].active相應的位獲得的.


雖然原則上能夠任意定義每一個softirq的函數,Linux內核爲了進一步增強延遲中斷功能,提出了tasklet的機制。tasklet實際上也就是一個函數。在第0個softirq的處理函數tasklet_hi_action中,咱們能夠看到,當執行這個函數的時候,會依次執行一個鏈表上全部的tasklet.


咱們大體上能夠把softirq的機制歸納成:

內核依次對32個softirq輪詢,若是遇到一個能夠執行而且須要的softirq,就執行對應的函數,這些函數有可能又會執行一個函數隊列。當執行完這個函數隊列後,纔會繼續詢問下一個softirq對應的函數。

 

掛上一個軟中斷

void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)

{

unsigned long flags;

int i;


spin_lock_irqsave(&softirq_mask_lock, flags);

softirq_vec[nr].data = data;

softirq_vec[nr].action = action;


for (i=0; i<NR_CPUS; i++)

softirq_mask(i) |= (1<<nr);

spin_unlock_irqrestore(&softirq_mask_lock, flags);

}

其中對每一個CPU的softirq_mask都標註一下,代表這個softirq被定義了。

tasklet

在這個32個softirq中,有的softirq的函數會依次執行一個隊列中的tasklet,如第一帖中圖所示。


tasklet其實就是一個函數。它的結構以下:


struct tasklet_struct

{

struct tasklet_struct *next;

unsigned long state;

atomic_t count;

void (*func)(unsigned long);

unsigned long data;

};


next 用於將tasklet串成一個隊列

state 表示一些狀態,後面詳細討論

count 用來禁用(count = 1 )或者啓用( count = 0 )這個tasklet.由於一旦一個tasklet被掛到隊列裏,若是沒有這個機制,它就必定會被執行。 這個count算是一個過後補救措施,萬一掛上了不想執行,就能夠把它置1。

func 即爲所要執行的函數。

data 因爲可能多個tasklet調用公用函數,所以用data能夠區分不一樣tasklet.


如何將一個tasklet掛上


首先要初始化一個tasklet,填上相應的參數


void tasklet_init(struct tasklet_struct *t,

void (*func)(unsigned long), unsigned long data)

{

t->func = func;

t->data = data;

t->state = 0;

atomic_set(&t->count, 0);

}


而後調用schedule函數,注意,下面的函數僅僅是將這個tasklet掛到 TASKLET_SOFTIRQ對應的軟中斷所執行的tasklet隊列上去, 事實上,還有其它的軟中斷,好比HI_SOFTIRQ,會執行其它的tasklet隊列,若是要掛上,那麼就要調用tasklet_hi_schedule(). 若是你本身寫的softirq執行一個tasklet隊列,那麼你須要本身寫相似下面的函數。


static inline void tasklet_schedule(struct tasklet_struct *t)

{

if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {

int cpu = smp_processor_id();

unsigned long flags;


local_irq_save(flags);

/**/ t->next = tasklet_vec[cpu].list;

/**/ tasklet_vec[cpu].list = t;

__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);

local_irq_restore(flags);

}

}


這個函數中/**/標註的句子用來掛接上tasklet,

__cpu_raise_softirq用來激活TASKLET_SOFTIRQ,這樣,下次執行do_softirq就會執行這個TASKLET_SOFTIRQ軟中斷了

__cpu_raise_softirq定義以下:


static inline void __cpu_raise_softirq(int cpu, int nr)

{

softirq_active(cpu) |= (1<<nr);

}


tasklet的運行方式


咱們以tasklet_action爲例,來講明tasklet運行機制。事實上,還有一個函數tasklet_hi_action一樣也運行tasklet隊列。

首先值得注意的是,咱們前面提到過,全部的cpu共用32個softirq,可是同一個softirq在不一樣的cpu上執行的數據是獨立的,基於這個原則,tasklet_vec對每一個cpu都有一個,每一個cpu都運行本身的tasklet隊列。

 

當執行一個tasklet隊列時,內核將這個隊列摘下來,以list爲隊列頭,而後從list的下一個開始依次執行。這樣作達到什麼效果呢?在執行這個隊列時,這個隊列的結構是靜止的,若是在運行期間,有中斷產生,而且往這個隊列裏添加tasklet的話,將填加到tasklet_vec[cpu].list中, 注意這個時候,這個隊列裏的任何tasklet都不會被執行,被執行的是list接管的隊列。


見/*1*//*2/之間的代碼。事實上,在一個隊列上同時添加和運行也是可行的,沒這個簡潔。


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

static void tasklet_action(struct softirq_action *a)

{

int cpu = smp_processor_id();

struct tasklet_struct *list;


/*1*/ local_irq_disable();

list = tasklet_vec[cpu].list;

tasklet_vec[cpu].list = NULL;

/*2*/ local_irq_enable();


while (list != NULL) {

struct tasklet_struct *t = list;


list = list->next;


/*3*/ if (tasklet_trylock(t)) {

if (atomic_read(&t->count) == 0) {

clear_bit(TASKLET_STATE_SCHED, &t->state);


t->func(t->data);

/*

* talklet_trylock() uses test_and_set_bit that imply

* an mb when it returns zero, thus we need the explicit

* mb only here: while closing the critical section.

*/

#ifdef CONFIG_SMP

/*?*/ smp_mb__before_clear_bit();

#endif

tasklet_unlock(t);

continue;

}

tasklet_unlock(t);

}

/*4*/ local_irq_disable();

t->next = tasklet_vec[cpu].list;

tasklet_vec[cpu].list = t;

__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);

/*5*/ local_irq_enable();

}

}

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


/*3*/看其它cpu是否還有同一個tasklet在執行,若是有的話,就首先將這個tasklet從新放到tasklet_vec[cpu].list指向的預備隊列(見/*4*/~/*5*/),然後跳過這個tasklet.

這也就說明了tasklet是不可重入的,以防止兩個相同的tasket訪問一樣的變量而產生競爭條件(race condition)


tasklet的狀態


在tasklet_struct中有一個屬性state,用來表示tasklet的狀態:

tasklet的狀態有3個:


1.當tasklet被掛到隊列上,尚未執行的時候,是 TASKLET_STATE_SCHED

2.當tasklet開始要被執行的時候,是 TASKLET_STATE_RUN

其它時候,則沒有這兩個位的設置


其實還有另外一對狀態,禁止或容許,tasklet_struct中用count表示,用下面的函數操做

 

 


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

static inline void tasklet_disable_nosync(struct tasklet_struct *t)

{

atomic_inc(&t->count);

}


static inline void tasklet_disable(struct tasklet_struct *t)

{

tasklet_disable_nosync(t);

tasklet_unlock_wait(t);

}


static inline void tasklet_enable(struct tasklet_struct *t)

{

atomic_dec(&t->count);

}

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


下面來驗證1,2這兩個狀態:


當被掛上隊列時:

首先要測試它是否已經被別的cpu掛上了,若是已經在別的cpu掛上了,則再也不將它掛上,不然設置狀態爲TASKLET_STATE_SCHED

 


static inline void tasklet_schedule(struct tasklet_struct *t)

{

if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {

... ...

}


爲何要這樣作?試想,若是一個tasklet已經掛在一隊列上,內核將沿着這個隊列一個個執行,如今若是又被掛到另外一個隊列上,那麼這個tasklet的指針

指向另外一個隊列,內核就會沿着它走到錯誤的隊列中去了。

 


tasklet開始執行時:


在tasklet_action中:

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

while (list != NULL) {

struct tasklet_struct *t = list;

/*0*/ list = list->next;


/*1*/ if (tasklet_trylock(t)) {

/*2*/ if (atomic_read(&t->count) == 0) {

/*3*/ clear_bit(TASKLET_STATE_SCHED, &t->state);


t->func(t->data);

/*

* talklet_trylock() uses test_and_set_bit that imply

* an mb when it returns zero, thus we need the explicit

* mb only here: while closing the critical section.

*/

#ifdef CONFIG_SMP

smp_mb__before_clear_bit();

#endif

/*4*/ tasklet_unlock(t);

continue;

}

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


1 看是不是別的cpu上這個tasklet已是 TASKLET_STATE_RUN了,若是是就跳過這個tasklet

2 看這個tasklet是否被容許運行?

3 清除TASKLET_STATE_SCHED,爲何如今清除,它不是尚未從隊列上摘下來嗎?事實上,它的指針已經再也不須要的,它的下一個tasklet已經被list記錄了(/*0*/)。這樣,若是其它cpu把它掛到其它的隊列上去一點影響都沒有。

4 清除TASKLET_STATE_RUN標誌


1和4確保了在全部cpu上,不可能運行同一個tasklet,這樣在必定程度上確保了tasklet對數據操做是安全的,可是不要忘了,多個tasklet可能指向同一個函數,因此仍然會發生競爭條件。


可能會有疑問:假設cpu 1上已經有tasklet 1掛在隊列上了,cpu2應該根本掛不上同一個tasklet 1,怎麼會有tasklet 1和它發生重入的狀況呢?

答案就在/*3*/上,當cpu 1的tasklet 1已經不是TASKLET_STATE_SCHED,而它還在運行,這時cpu2徹底有可能掛上同一個tasklet 1,並且使得它試圖運行,這時/*1*/的判斷就起做用了。


軟中斷的重入

 


如圖,通常狀況下,在硬件中斷處理程序後都會試圖調用do_softirq執行軟中斷,可是若是發現如今已經有中斷在運行,或者已經有軟中斷在運行,則

再也不運行本身調用的中斷。也就是說,軟中斷是不能進入硬件中斷部分的,而且軟中斷在一個cpu上是不可重入的,或者說是串行化的(serialize)


其目的是避免訪問一樣的變量致使競爭條件的出現。在開中斷的中斷處理程序中不容許調用軟中斷多是但願這個中斷處理程序儘快結束。


這是由do_softirq中的


if (in_interrupt())

return;


保證的.


其中,

#define in_interrupt() ({ int __cpu = smp_processor_id(); /

(local_irq_count(__cpu) + local_bh_count(__cpu) != 0); })


前者local_irq_count(_cpu):


當進入硬件中斷處理程序時,handle_IRQ_event中的irq_enter(cpu, irq)會將它加1,代表又進入一個硬件中斷

退出則調用irq_exit(cpu, irq)


後者local_bh_count(__cpu) :


當進入軟中斷處理程序時,do_softirq中的local_bh_disable()會將它加1,代表處於軟中斷中

local_bh_disable();


一個例子:


當內核正在執行處理定時器的軟中斷時,這期間可能會發生多個時鐘中斷,這些時鐘中斷的處理程序都試圖再次運行處理定時器的軟中斷,可是因爲 已經有個軟中斷在運行了,因而就放棄返回。


軟中斷調用時機


最直接的調用:


當硬中斷執行完後,迅速調用do_softirq來執行軟中斷(見下面的代碼),這樣,被硬中斷標註的軟中斷能得以迅速執行。固然,不是每次調用都成功的,見前面關於重入的帖子。

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

asmlinkage unsigned int do_IRQ(struct pt_regs regs)

{

... ...

if (softirq_active(cpu) & softirq_mask(cpu))

do_softirq();


}

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


還有,不是每一個被標註的軟中斷都能在此次陷入內核的部分中完成,可能會延遲到下次中斷。


其它地方的調用:


在entry.S中有一個調用點:


handle_softirq:

call SYMBOL_NAME(do_softirq)

jmp ret_from_intr


有兩處調用它,一處是當系統調用處理完後:


ENTRY(ret_from_sys_call)

#ifdef CONFIG_SMP

movl processor(%ebx),%eax

shll $CONFIG_X86_L1_CACHE_SHIFT,%eax

movl SYMBOL_NAME(irq_stat)(,%eax),%ecx # softirq_active

testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx # softirq_mask

#else

movl SYMBOL_NAME(irq_stat),%ecx # softirq_active

testl SYMBOL_NAME(irq_stat)+4,%ecx # softirq_mask

#endif

jne handle_softirq


一處是當異常處理完後:


ret_from_exception:

#ifdef CONFIG_SMP

GET_CURRENT(%ebx)

movl processor(%ebx),%eax

shll $CONFIG_X86_L1_CACHE_SHIFT,%eax

movl SYMBOL_NAME(irq_stat)(,%eax),%ecx # softirq_active

testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx # softirq_mask

#else

movl SYMBOL_NAME(irq_stat),%ecx # softirq_active

testl SYMBOL_NAME(irq_stat)+4,%ecx # softirq_mask

#endif

jne handle_softirq


注意其中的irq_stat, irq_stat +4 對應的就是字段 active和mask


既然咱們每次調用完硬中斷後都立刻調用軟中斷,爲何還要在這裏調用呢?


緣由可能都多方面的:


(1)在系統調用或者異常處理中一樣能夠標註軟中斷,這樣它們在返回前就能得以迅速執行


(2)前面提到,有些軟中斷要延遲到下次陷入內核才能執行,系統調用和異常都陷入內核,因此能夠儘早的把軟中斷處理掉


(3)若是在異常或者系統調用中發生中斷,那麼前面提到,可能還會有一些軟中斷沒有處理,在這兩個地方作一個補救工做,儘可能避免到下次陷入內核才處理這些軟中斷。


另外,在切換前也調用。


bottom half


2.2.x中的bottom half :


2.2.x版本中的bottom half就至關於2.4.1中的softirq.它的問題在於只有32個,若是要擴充的話,須要task 隊列(這裏task不是進程,而是函數),還有一個比較大的問題,就是雖然bottom half在一個CPU上是串行的(由local_bh_count[cpu]記數保證),可是在多CPU上是不安全的,例如,一個CPU上在運行關於定時器的bottom half,另外一個CPU也能夠運行同一個bottom half,出現了重入。


2.4.1中的bottom half


2.4.1中,用tasklet表示bottom half, mark_bh就是將相應的tasklet掛到運行隊列裏tasklet_hi_vec[cpu].list,這個隊列由HI_SOFTIRQ對應的softirq來執行。


另外,用一個全局鎖來保證,當一個CPU上運行一個bottom half時,其它CPU上不能運行任何一個bottom half。這和之前的bottom half有所不一樣,不知道是否我看錯了。


用32個tasklet來表示bottom half:


struct tasklet_struct bh_task_vec[32];


首先,初始化全部的bottom half:


void __init softirq_init()

{

... ...

for (i=0; i<32; i++)

tasklet_init(bh_task_vec+i, bh_action, i);

... ...

}


這裏bh_action是下面的函數,它使得bottom half運行對應的bh_base。


static void bh_action(unsigned long nr)

{

int cpu = smp_processor_id();


/*1*/ if (!spin_trylock(&global_bh_lock))

goto resched;


if (!hardirq_trylock(cpu))

goto resched_unlock;


if (bh_base[nr])

bh_base[nr]();


hardirq_endlock(cpu);

spin_unlock(&global_bh_lock);

return;


resched_unlock:

spin_unlock(&global_bh_lock);

resched:

mark_bh(nr);

}


/*1*/試圖上鎖,若是得不到鎖,則從新將bottom half掛上,下次在運行。

 

當要定義一個bottom half時用下面的函數:


void init_bh(int nr, void (*routine)(void))

{

bh_base[nr] = routine;

mb();

}


取消定義時,用:


void remove_bh(int nr)

{

tasklet_kill(bh_task_vec+nr);

bh_base[nr] = NULL;

}


tasklet_kill確保這個tasklet被運行了,於是它的指針也沒有用了。


激活一個bottom half,就是將它掛到隊列中 :


static inline void mark_bh(int nr)

{

tasklet_hi_schedule(bh_task_vec+nr);

}

[目錄]


定時器代碼分析


時鐘和定時器中斷

系統啓動核心時,調用start_kernal()繼續各方面的初始化,在這以前,各類中斷都被禁止,只有在完成必要的初始化後,直到執行完Kmalloc_init()後,才容許中斷(init/main.c)。與時鐘中斷有關的部分初始化以下:
調用trap_init()設置各類trap入口,如system_call、GDT entry、LDT entry、call gate等。其中,0~17爲各類錯誤入口,18~47保留。
調用init_IRQ()函數設置核心系統的時鐘週期爲10ms,即100HZ,它是之後按照輪轉法進行CPU調度時所依照的基準時鐘週期。每10ms產生的時鐘中斷信號直接輸入到第一塊8259A的INT 0(即irq0)。初始化中斷矢量表中從0x20起的17箇中斷矢量,用bad_IRQ#_interrupt函數的地址(#爲中斷號)填寫。
調用sched_init()函數,設置啓動第一個進程init_task。設置用於管理bottom_half機制的數據結構bh_base[],規定三類事件的中斷處理函數,即時鐘TIMER_BH、設備TQUEUE_BH和IMMEDIATE_BH。
調用time_init()函數,首先讀取當時的CMOS時間,最後調用setup_x86_irq(0,&irq0)函數,把irq0掛到irq_action[0]隊列的後面,並把中斷矢量表中第0x20項,即timer中斷對應的中斷矢量改成IRQ0_interrupt函數的地址,在irq0中,指定時間中斷服務程序是timer_interrupt,
     static struct irqaction irq0  = { timer_interrupt, 0, 0, "timer", NULL, NULL}
    結構irqaction的定義以下:
        struct irqaction {
            void (*handler)(int, void *, struct pt_regs *);  /* 中斷服務函數入口 */
            unsigned long flags;                      /* 服務允中與否標記 */
        unsigned long mask;
            const char *name;
            void *dev_id;
          struct irqaction *next;
    };
其中,若flag==SA_INTERRUPT,則中斷矢量改成fast_IRQ#_interrupt,在執行中斷服務的過程當中不容許出現中斷,若爲其它標記,則中斷矢量爲IRQ#_interrupt,在執行中斷服務的過程當中,容許出現中斷。
Irq_action的定義與初始化以下:
    static void (*interrupt[17])(void) = {IRQ#_interrupt};
            static void (*fast_interrupt[16])(void) = {fast_IRQ#_interrupt};
    static void (*bad_interrupt[16])(void) = {bad_IRQ#_interrupt};(以上#爲中斷號)
    static struct irqaction *irq_action[16] = {
            NULL, NULL, NULL, NULL,
            NULL, NULL, NULL, NULL,
            NULL, NULL, NULL, NULL,
            NULL, NULL, NULL, NULL
    };

irq_action是一個全局數組,每一個元素指向一個irq隊列,共16個irq隊列,時鐘中斷請求隊列在第一個隊列,即irq_action[0]。當每一箇中斷請求到來時,都調用setup_x86_irq把該請求掛到相應的隊列的後面。

之後,系統每10ms產生一次時鐘中斷信號,該信號直接輸入到第一塊8259A的INT 0(即irq0)。CPU根據中斷矢量表和中斷源,找到中斷矢量函數入口IRQ0_interrupt(程序運行過程當中容許中斷)或者fast_IRQ0_interrupt(程序運行過程當中不容許中斷)或者bad_IRQ0_interrupt(不執行任何動做,直接返回),這些函數由宏BUILD_TIMER_IRQ(chip, nr, mask)展開定義。
宏BUILD_TIMER_IRQ(chip, nr, mask)的定義以下:
#define BUILD_TIMER_IRQ(chip,nr,mask) /
asmlinkage void IRQ_NAME(nr); /
asmlinkage void FAST_IRQ_NAME(nr); /
asmlinkage void BAD_IRQ_NAME(nr); /
__asm__( /
"/n"__ALIGN_STR"/n" /
SYMBOL_NAME_STR(fast_IRQ) #nr "_interrupt:/n/t" /
SYMBOL_NAME_STR(bad_IRQ) #nr "_interrupt:/n/t" /
SYMBOL_NAME_STR(IRQ) #nr "_interrupt:/n/t" /
        "pushl $-"#nr"-2/n/t" /
        SAVE_ALL /
        ENTER_KERNEL /
        ACK_##chip(mask,(nr&7)) /
        "incl "SYMBOL_NAME_STR(intr_count)"/n/t"/  /* intr_count爲進入臨界區的同步信號量 */
        "movl %esp,%ebx/n/t" /
        "pushl %ebx/n/t" /
        "pushl $" #nr "/n/t" /                                                /* 把do_irq函數參數壓進堆棧 */
        "call "SYMBOL_NAME_STR(do_IRQ)"/n/t" /
        "addl $8,%esp/n/t" /
        "cli/n/t" /
        UNBLK_##chip(mask) /
        "decl "SYMBOL_NAME_STR(intr_count)"/n/t" /
        "incl "SYMBOL_NAME_STR(syscall_count)"/n/t" /
        "jmp ret_from_sys_call/n");
其中nr爲中斷請求類型,取值0~15。在irq.c中經過語句BUILD_TIMER_IRQ(first, 0, 0x01)調用該宏,在執行宏的過程當中處理時鐘中斷響應程序do_irq()。
函數do_irq()的第一個參數是中斷請求隊列序號,時鐘中斷請求傳進來的該參數是0。因而程序根據參數0找到請求隊列irq_action[0],逐個處理該隊列上handler所指的時鐘中斷請求的服務函數。因爲已經指定時鐘中斷請求的服務函數是timer_interrupt,在函數timer_interrupt中,當即調用do_timer()函數。
函數do_timer()把jiffies和lost_ticks加1,接着就執行mark_bh(TIMER_BH)函數,把bottom_half中時鐘隊列對應的位置位,表示該隊列處於激活狀態。在作完這些動做後,程序從函數do_irq()中返回,繼續執行之後的彙編代碼。因而,程序在執行語句jmp ret_from_sys_call後,跳到指定的位置處繼續執行。
代碼段jmp ret_from_sys_call及其相關的代碼段以下:
        ALIGN
        .globl ret_from_sys_call
ret_from_sys_call:
        cmpl $0,SYMBOL_NAME(intr_count)
        jne 2f
9:        movl SYMBOL_NAME(bh_mask),%eax
        andl SYMBOL_NAME(bh_active),%eax
        jne handle_bottom_half
#ifdef __SMP__
        cmpb $(NO_PROC_ID), SYMBOL_NAME(saved_active_kernel_processor)
        jne 2f
#endif
        movl EFLAGS(%esp),%eax                # check VM86 flag: CS/SS are
        testl $(VM_MASK),%eax                # different then
        jne 1f
        cmpw $(KERNEL_CS),CS(%esp)        # was old code segment supervisor ?
        je 2f
1:        sti
        orl $(IF_MASK),%eax                # these just try to make sure
        andl $~NT_MASK,%eax                # the program doesn't do anything
        movl %eax,EFLAGS(%esp)                # stupid
        cmpl $0,SYMBOL_NAME(need_resched)
        jne reschedule
#ifdef __SMP__
        GET_PROCESSOR_OFFSET(%eax)
        movl SYMBOL_NAME(current_set)(,%eax), %eax
#else
        movl SYMBOL_NAME(current_set),%eax
#endif
        cmpl SYMBOL_NAME(task),%eax        # task[0] cannot have signals
        je 2f
        movl blocked(%eax),%ecx
        movl %ecx,%ebx                        # save blocked in %ebx for signal handling
        notl %ecx
        andl signal(%eax),%ecx
        jne signal_return
2:        RESTORE_ALL

ALIGN
signal_return:
        movl %esp,%ecx
        pushl %ecx
        testl $(VM_MASK),EFLAGS(%ecx)
        jne v86_signal_return
        pushl %ebx
        call SYMBOL_NAME(do_signal)
        popl %ebx
        popl %ebx
        RESTORE_ALL

ALIGN
v86_signal_return:
        call SYMBOL_NAME(save_v86_state)
        movl %eax,%esp
        pushl %eax
        pushl %ebx
        call SYMBOL_NAME(do_signal)
        popl %ebx
        popl %ebx
        RESTORE_ALL

  handle_bottom_half:
incl SYMBOL_NAME(intr_count)
call SYMBOL_NAME(do_bottom_half)
decl SYMBOL_NAME(intr_count)
jmp 9f

ALIGN
reschedule:
pushl $ret_from_sys_call
  jmp SYMBOL_NAME(schedule)    # test

上述彙編代碼用流程圖表示以下:

                 ret_from_sys_call

 

 

 

 

 

 

 

 

 

 

 

 

 

另外,一些與時鐘中斷及bottom half機制有關的數據結構介紹以下:
#define        HZ        100
unsigned long volatile jiffies=0;
系統每隔10ms自動把它加1,它是核心系統計時的單位。
enum {
        TIMER_BH = 0,
        CONSOLE_BH,
        TQUEUE_BH,
        DIGI_BH,
        SERIAL_BH,
        RISCOM8_BH,
SPECIALIX_BH,
        BAYCOM_BH,
        NET_BH,
        IMMEDIATE_BH,
        KEYBOARD_BH,
        CYCLADES_BH,
        CM206_BH
};
如今只定義了13個bottom half隊列,未來可擴充到32個隊列。
unsigned long intr_count = 0;
至關於信號量的做用。只有其等於0,才能夠do_bottom_half。
int bh_mask_count[32];
用來計算bottom half隊列被屏蔽的次數。只有某隊列的bh_mask_count數爲0,才能enable該隊列。
unsigned long bh_active = 0;
bh_active是32位長整數,每一位表示一個bottom half隊列,該位置1,表示該隊列處於激活狀態,隨時準備在CPU認爲合適的時候執行該隊列的服務,置0則相反。
unsigned long bh_mask = 0;
bh_mask也是32位長整數,每一位對應一個bottom half隊列,該位置1,表示該隊列可用,並把處理函數的入口地址賦給bh_base,置0則相反。
void (*bh_base[32])(void);
bottom half服務函數入口地址數組。定時器處理函數擁有最高的優先級,它的地址存放在bh_base[0],老是最早執行它所指向的函數。

咱們注意到,在IRQ#_interrupt和fast_IRQ#_interrupt中斷函數處理返回前,都經過語句jmp ret_from_sys_call,跳到系統調用的返回處(見irq.h),若是bottom half隊列不爲空,則在那裏作相似:
           if (bh_active & bh_mask) {
                            intr_count = 1;
                            do_bottom_half();
                            intr_count = 0;
                    }(該判斷的彙編代碼見Entry.S)
的判斷,調用do_bottom_half()函數。
在CPU調度時,經過schedule函數執行上述的判斷,再調用do_bottom_half()函數。
總而言之,在下列三種時機:
CPU調度時
系統調用返回前
中斷處理返回前
都會做判斷調用do_bottom_half函數。Do_bottom_half函數依次掃描32個隊列,找出須要服務的隊列,執行服務後把對應該隊列的bh_active的相應位置0。因爲bh_active標誌中TIMER_BH對應的bit爲1,於是系統根據服務函數入口地址數組bh_base找到函數timer_bh()的入口地址,並立刻執行該函數,在函數timer_bh中,調用函數run_timer_list()和函數run_old_timers()函數,定時執行服務。

TVECS結構及其實現
有關TVECS結構的一些數據結構定義以下:

#define TVN_BITS 6
#define TVR_BITS 8
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)

#define SLOW_BUT_DEBUGGING_TIMERS 0

struct timer_vec {
        int index;
        struct timer_list *vec[TVN_SIZE];
};
struct timer_vec_root {
        int index;
        struct timer_list *vec[TVR_SIZE];
};

static struct timer_vec tv5 = { 0 };
static struct timer_vec tv4 = { 0 };
static struct timer_vec tv3 = { 0 };
static struct timer_vec tv2 = { 0 };
static struct timer_vec_root tv1 = { 0 };

static struct timer_vec * const tvecs[] = {
        (struct timer_vec *)&tv1, &tv2, &tv3, &tv4, &tv5
};
#define NOOF_TVECS (sizeof(tvecs) / sizeof(tvecs[0]))
static unsigned long timer_jiffies = 0;

TVECS結構是一個元素個數爲5的數組,分別指向tv1,tv2,tv3,tv4,tv5的地址。其中,tv1是結構timer_vec_root的變量,它有一個index域和有256個元素的指針數組,該數組的每一個元素都是一條類型爲timer_list的鏈表。其他四個元素都是結構timer_vec的變量,它們各有一個index域和64個元素的指針數組,這些數組的每一個元素也都是一條鏈表。

函數internal_add_timer(struct timer_list *timer)

函數代碼以下:
static inline void internal_add_timer(struct timer_list *timer)
{
        /*
        * must be cli-ed when calling this
        */
        unsigned long expires = timer->expires;
        unsigned long idx = expires - timer_jiffies;

        if (idx < TVR_SIZE) {
                int i = expires & TVR_MASK;
                insert_timer(timer, tv1.vec, i);
        } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
                int i = (expires >> TVR_BITS) & TVN_MASK;
                insert_timer(timer, tv2.vec, i);
        } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
                int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
                insert_timer(timer, tv3.vec, i);
        } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
                int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
                insert_timer(timer, tv4.vec, i);
        } else if (expires < timer_jiffies) {
                /* can happen if you add a timer with expires == jiffies,
                * or you set a timer to go off in the past
                */
                insert_timer(timer, tv1.vec, tv1.index);
        } else if (idx < 0xffffffffUL) {
                int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
                insert_timer(timer, tv5.vec, i);
        } else {
                /* Can only get here on architectures with 64-bit jiffies */
                timer->next = timer->prev = timer;
        }
}

   expires


在調用該函數以前,必須關中。對該函數的說明以下:
取出要加進TVECS的timer的激發時間(expires),算出expires與timer_jiffies的差值idx,用來決定該插到哪一個隊列中去。
若idx小於2^8,則取expires的第0位到第7位的值I,把timer加到tv1.vec中第I個鏈表的第一個表項以前。
若idx小於2^14,則取expires的第8位到第13位的值I,把timer加到tv2.vec中第I個鏈表的第一個表項以前。
若idx小於2^20,則取expires的第14位到第19位的值I,把timer加到tv3.vec中第I個鏈表的第一個表項以前。
若idx小於2^26,則取expires的第20位到第25位的值I,把timer加到tv4.vec中第I個鏈表的第一個表項以前。
若expires小於timer_jiffies,即idx小於0,則代表該timer到期,應該把timer放入tv1.vec中tv1.index指定的鏈表的第一個表項以前。
若idx小於2^32,則取expires的第26位到第32位的值I,把timer加到tv5.vec中第I個鏈表的第一個表項以前。
若idx大等於2^32,該狀況只有在64位的機器上纔有可能發生,在這種狀況下,不把timer加入TVECS結構。

函數cascade_timers(struct timer_vec *tv)

該函數只是把tv->index指定的那條鏈表上的全部timer調用internal_add_timer()函數進行從新調整,這些timer將放入TVECS結構中比原來位置往前移一級,好比說,tv4上的timer將放到tv3上去,tv2上的timer將放到tv1上。這種前移是由run_timer_list函數裏調用cascade_timers函數的時機來保證的。而後把該條鏈表置空,tv->index加1,若tv->index等於64,則從新置爲0。

函數run_timer_list()

函數代碼以下:
static inline void run_timer_list(void)
{
cli();
while ((long)(jiffies - timer_jiffies) >= 0) {
        struct timer_list *timer;
        if (!tv1.index) {
                int n = 1;
                do {
                        cascade_timers(tvecs[n]);
                } while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);
        }
        while ((timer = tv1.vec[tv1.index])) {
                void (*fn)(unsigned long) = timer->function;
                unsigned long data = timer->data;
                detach_timer(timer);
                timer->next = timer->prev = NULL;
                sti();
                fn(data);
                cli();
        }
        ++timer_jiffies;
        tv1.index = (tv1.index + 1) & TVR_MASK;
}
sti();
}
對run_timer_list函數的說明以下:
關中。
判斷jiffies是否大等於timer_jiffies,若不是,goto 8。
判斷tv1.index是否爲0(即此時系統已經掃描過整個tv1的256個timer_list鏈表,又回到的第一個鏈表處,此時需重整TVECS結構),如果,置n爲1;若不是,goto 6。
調用cascade_timers()函數把TVECS[n]中由其index指定的那條鏈表上的timer放到TVECS[n-1]中來。注意:調用cascade_timers()函數後,index已經加1。
判斷TVECS[n]->index是否爲1,即原來爲0。若是是(代表TVECS[n]上全部都已經掃描一遍,此時需對其後一級的TVECS[++n]調用cascade_timers()進行重整),把n加1,goto 4。
執行tv1.vec上由tv1->index指定的那條鏈表上的全部timer的服務函數,並把該timer從鏈表中移走。在執行服務函數的過程當中,容許中斷。
timer_jiffies加1,tv1->index加1,若tv1->index等於256,則從新置爲0,goto 2。
開中,返回。

Linux提供了兩種定時器服務。一種早期的由timer_struct等結構描述,由run_old_times函數處理。另外一種「新」的服務由timer_list等結構描述,由add_timer、del_timer、cascade_time和run_timer_list等函數處理。
早期的定時器服務利用以下數據結構:
struct timer_struct {
    unsigned long expires;  /*本定時器被喚醒的時刻 */
    void (*fn)(void);       /* 定時器喚醒後的處理函數 */
}
struct timer_struct timer_table[32];  /*最多可同時啓用32個定時器 */
unsigned long timer_active;        /* 每位對應必定時器,置1表示啓用 */
新的定時器服務依靠鏈表結構突破了32個的限制,利用以下的數據結構:
struct timer_list {
    struct timer_list *next;
    struct timer_list *prev;
    unsigned long expires;
    unsigned long data;          /* 用來存放當前進程的PCB塊的指針,可做爲參數傳
    void (*function)(unsigned long);  給function */
}


表示上述數據結構的圖示以下:


    在這裏,順便簡單介紹一下舊的timer機制的運做狀況。
    系統在每次調用函數do_bottom_half時,都會調用一次函數run_old_timers()。
函數run_old_timers()
該函數處理的很簡單,只不過依次掃描timer_table中的32個定時器,若掃描到的定時器已經到期,而且已經被激活,則執行該timer的服務函數。

間隔定時器itimer
系統爲每一個進程提供了三個間隔定時器。當其中任意一個定時器到期時,就會發出一個信號給進程,同時,定時器從新開始運做。三種定時器描述以下:
ITIMER_REAL  真實時鐘,到期時送出SIGALRM信號。
ITIMER_VIRTUAL  僅在進程運行時的計時,到期時送出SIGVTALRM信號。
ITIMER_PROF  不只在進程運行時計時,在系統爲進程運做而運行時它也計時,與ITIMER_VIRTUAL對比,該定時器一般爲那些在用戶態和核心態空間運行的應用所花去的時間計時,到期時送出SIGPROF信號。
與itimer有關的數據結構定義以下:
struct timespec {
        long        tv_sec;                /* seconds */
        long        tv_nsec;        /* nanoseconds */
};
struct timeval {
        int        tv_sec;                /* seconds */
        int        tv_usec;        /* microseconds */
};
struct  itimerspec {
        struct  timespec it_interval;    /* timer period */
        struct  timespec it_value;       /* timer expiration */
};
struct        itimerval {
        struct        timeval it_interval;        /* timer interval */
        struct        timeval it_value;        /* current value */
};

這三種定時器在task_struct中定義:
struct task_struct {
    ……
    unsigned long timeout;
    unsigned long it_real_value,it_prof_value,it_virt_value;
    unsigned long it_real_incr,it_prof_incr,it_virt_incr;
    struct timer_list real_timer;
    ……
}
在進程建立時,系統把it_real_fn函數的入口地址賦給real_timer.function。(見sched.h)
咱們小組分析了三個系統調用:sys_getitimer,sys_setitimer,sys_alarm。
在這三個系統調用中,需用到如下一些函數:
函數static int _getitimer(int which, struct itimerval *value)
該函數的運行過程大體以下:
根據傳進的參數which按三種itimer分別處理:
如果ITIMER_REAL,則設置interval爲current進程的it_real_incr,val設置爲0;判斷current進程的real_timer有否設置並掛入TVECS結構中,如有,設置val爲current進程real_timer的expires,並把real_timer從新掛到TVECS結構中,接着把val與當前jiffies做比較,若小等於當前jiffies,則說明該real_timer已經到期,因而從新設置val爲當前jiffies的值加1。最後把val減去當前jiffies的值,goto 2。
如果ITIMER_VIRTUAL,則分別設置interval,val的值爲current進程的it_virt_incr、it_virt_value,goto 2。
如果ITIMER_PROF,則分別設置interval,val的值爲current進程的it_prof_incr、it_prof_value,goto 2。
   (2)調用函數jiffiestotv把val,interval的jiffies值轉換爲timeval,返回0。
函數 int _setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
該函數的運行過程大體以下:
調用函數tvtojiffies把value中的interval和value轉換爲jiffies i 和 j。
判斷指針ovalue是否爲空,若空,goto ;若不空,則把由which指定類型的itimer存入ovalue中,若存放不成功,goto 4;
根據which指定的itimer按三種類型分別處理:
如果ITIMER_REAL,則從TVECS結構中取出current進程的real_timer,並從新設置current進程的it_real_value和it_real_incr爲j和i。若j等於0,goto 4;若不等於0,則把當前jiffies的值加上定時器剩餘時間j,獲得觸發時間。若i小於j,則代表I已經溢出,應該從新設爲ULONG_MAX。最後把current進程的real_timer的expires設爲i,把設置過的real_timer從新加入TVECS結構,goto 4。
如果ITIMER_VIRTUAL,則設置current進程的it-_virt_value和it_virt_incr爲j和i。
如果ITIMER_PROF,則設置current進程的it-_prof_value和it_prof_incr爲j和i。
   (4)返回0。

函數verify_area(int type, const void *addr, unsigned long size)
該函數的主要功能是對以addr爲始址的,長度爲size的一塊存儲區是否有type類型的操做權利。

函數memcpy_tofs(to, from, n)
該函數的主要功能是從以from爲始址的存儲區中取出長度爲n的一塊數據放入以to爲始址的存儲區。

函數memcpy_fromfs(from, to, n)
該函數的主要功能是從以from爲始址的存儲區中取出長度爲n的一塊數據放入以to爲始址的存儲區。

函數memset((char*)&set_buffer, 0, sizeof(set_buffer))
該函數的主要功能是把set_buffer中的內容置爲0,在這裏,即把it_value和it_interval置爲0。

如今,我簡單介紹一下這三個系統調用:
系統調用sys_getitimer(int which, struct itimerval *value)

首先,若value爲NULL,則返回-EFAULT,說明這是一個bad address。
其次,把which類型的itimer取出放入get_buffer。
再次,若存放成功,再確認對value的寫權利。
最後,則把get_buffer中的itimer取出,拷入value。

系統調用sys_setitimer(int which, struct itimerval *value,struct itimerval *ovalue)

首先,判斷value是否爲NULL,若不是,則確認對value是否有讀的權利,並把set_buffer中的數據拷入value;若value爲NULL,則把set_buffer中的內容置爲0,即把it_value和it_interval置爲0。
其次,判斷ovalue是否爲NULL,若不是,則確認對ovalue是否有寫的權利。
再次,調用函數_setitimer設置由which指定類型的itimer。
最後,調用函數memcpy_tofs把get_buffer中的數據拷入ovalue,返回。

系統調用sys_alarm(unsigned int seconds)

該系統調用從新設置進程的real_itimer,若seconds爲0,則把原先的alarm定時器刪掉。而且設interval爲0,故只觸發一次,並把舊的real_timer存入oldalarm,並返回oldalarm。

[目錄]


from lisolog

 

[目錄]


index


中斷流程

中斷能夠用下面的流程來表示:

中斷產生源 --------> 中斷向量表 (idt) -----------> 中斷入口 ( 通常簡單處理後調用相應的函數) ---------> 後續處理


根據中斷產生源,咱們能夠把中斷分紅兩個部分 :

內部中斷( CPU 產生)
外部中斷( 外部硬件產生 )

這些中斷通過一些處理後,會有一些後續處理。

後面分別討論:

內部中斷
外部中斷
後續處理

[目錄]


內部中斷


內部中斷

內部中斷有兩種產生方式:

CPU 自發產生的: 如除數爲0 的中斷, page_fault 等
程序調用 int : int 80h

CPU自發產生的中斷對應 idt 向量表中肯定的位置,例如除數爲0的中斷在對應idt中第0個向量,
所以,內核只須要在第0個向量中設定相應的處理函數便可。

程序調用 int 能夠產生的任何中斷, 所以,前者是後者的子集。 特別的有:

int 80h

這是系統調用的中斷.( system call )是用戶代碼調用內核代碼的入口。

這裏面能夠考察的專題至少有:

*系統調用
*其它內部中斷

[目錄]


外部中斷


外部中斷

1.
外部中斷是: 外部硬件(如時鐘) -----> 中斷芯片 ----> 中斷向量表 -----> 中斷入口

完成一個完整的映射,有4件事情要作:

(1) 將外部設備和中斷芯片相應的管腳接上
(2) 對中斷芯片設置,使得特定管腳的中斷能映射到CPU idt特定的位置
(3) 程序中包含了這些中斷入口
(4) 在中斷向量表裏設置向量相應的入口地址

這些工做須要在外部中斷流程裏描述

2.
因爲硬件設備可能共用一箇中斷,在統一的函數中會有相應的結構來處理,也就是有16個結構分別處理相應的16箇中斷
特定的硬件驅動須要將本身的處理函數掛接到特定的結構上.

3.
可是,有一個問題:

驅動怎麼知道本身的硬件產生哪一個中斷?

有一些是肯定的,好比時鐘是第0個, 軟盤是第 5 個(right ??), 還有一些 PCI 設備是能夠經過訪問獲得它們的中斷號的,可是ISA設備須要經過探測(probe)來獲得(詳細狀況能夠參考 linux device driver )這涉及探測的工做

4.
所以,這裏面要考察的工做至少包括:

1. i8259芯片的設置(包括上面的 (2) ), 以及一些其它屬性的設置
2. 外部中斷的流程
3. 處理外部中斷的結構與相應的數據結構

 


下面是《LINUX系統分析...》中的一段,可供參考。我也有些疑惑,快下班了就明天說吧。
但有時一個設備驅動程序不知道設備將使用
哪個中斷。在P C I 結構中這不會成爲一個問題,由於P C I 的設備驅動程序老是知道它們的中
斷號。但對於I S A 結構而言,一個設備驅動程序找到本身使用的中斷號卻並不容易。L i n u x 系統
經過容許設備驅動程序探測本身的中斷來解決這個問題。
首先,設備驅動程序使得設備產生一箇中斷。而後,容許系統中全部沒有指定的中斷,這
意味着設備掛起的中斷將會經過中斷控制器傳送。L i n u x 系統讀取中斷狀態寄存器而後將它的
值返回到設備驅動程序。一個非0 的結果意味着在探測期間發生了一個或者多個的中斷。設備
驅動程序如今能夠關閉探測,這時全部還未被指定的中斷將繼續被禁止。
一個ISA 設備驅動程序知道了它的中斷號之後,就能夠請求對中斷的控制了。
PCI 結構的系統中斷比I S A 結構的系統中斷要靈活得多。I S A 設備使用中斷插腳常用跳
線設置,因此在設備驅動程序中是固定的。但P C I 設備是在系統啓動過程當中P C I 初始化時由P C I
BIOS 或PCI 子系統分配的。每個P C I 設備都有可能使用A 、B 、C 或者D 這4 箇中斷插腳中的
一個。缺省狀況下設備使用插腳A 。
每一個P C I 插槽的P C I 中斷A 、B 、C 和D 是經過路由選擇鏈接到中斷控制器上的。因此P C I 插
槽4 的插腳A 可能鏈接到中斷控制器的插腳6 ,P C I 插槽4 的插腳B 可能鏈接到中斷控制器的插腳
7 ,以此類推。
P C I 中斷具體如何進行路由通常依照系統的不一樣而不一樣,但系統中必定存在P C I 中斷路由拓
撲結構的設置代碼。在Intel PC 機中,系統的B I O S 代碼負責中斷的路由設置。對於沒有B I O S 的
系統,L i n u x 系統內核負責設置。
P C I 的設置代碼將中斷控制器的插腳號寫入到每一個設備的PCI 設置頭中。P C I 的設置代碼根
據所知道的P C I 中斷路由拓撲結構、P C I 設備使用的插槽,以及正在使用的P C I 中斷的插腳號來
決定中斷號,也就是I R Q 號。
系統中能夠有不少的P C I 中斷源,例如當系統使用了P C I - P C I 橋時。這時,中斷源的數目可
能超過了系統可編程中斷控制器上插腳的數目。在這種狀況下,某些P C I 設備之間就不得不共
享一箇中斷,也就是說,中斷控制器上的某一個插腳能夠接收來自幾個設備的中斷。L i n u x 系
統經過讓第一個中斷源請求者宣佈它使用的中斷是否能夠被共享來實現中斷在幾個設備之間共享的。中斷共享使得i r q _ a c t i o n 數組中的同一個入口指向幾個設備的i r q a c t i o n 結構。當一個共享
的中斷有中斷髮生時,L i n u x 系統將會調用和此中斷有關的全部中斷處理程序。全部能夠共享
中斷的設備驅動程序的中斷處理程序均可能在任什麼時候候被調用,即便在自身沒有中斷須要處理
時。

 

 

[目錄]


後續處理


後續處理
後續部分主要完成下面的工做

1. bottom_half
2. 是否能進程切換?
3.是否須要進程切換?是則切換
4.信號處理

特別的,有一個重要的下半部分就是時鐘中斷的下半部分。

bottom_half

正如許多書所說的,它們繼續完成中斷處理(在開中斷的狀態下), 所以中斷中的處理函數須要在一個32位變量中設置特定的bit來告訴do_softirq要執行哪一個bottom_half
(咱們不妨把這個32位數想象成一個新的中斷向量表,設置bit至關於產生中斷,下半部分至關於handler,也許這是被稱爲軟中斷的緣由吧)

bottom_half有的時候須要藉助一個特殊的結構: task_queue 來完成更多的工做,

-----------------------------------
task_queue

task_queue 是一個鏈表,每一個節點是一個函數指針,這樣,一 個 bottom_half 就能夠執行一個鏈表上的函數列了
固然 task_queue 不必定只在 bottom_half 中應用, 我查了一下, 在一些驅動中也直接調用 run_task_queue 來執行一個特定的隊列.

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

若是內核須要在某個中斷產生後執行它的函數,只須要在它下半部分調用的 task_queue 上掛上它的函數( Linux Device Driver 中有步進馬達的例子)
如今的內核有些變化,增長了softirq_action tasklet, 不十分清楚是什麼緣由

是否須要進行切換

由於 linux是非搶佔的,因此若是返回的代碼段是內核級的話,就不容許進行切換。
若是能切換判斷一下是否須要切換, 若是是就切換

信號處理

看是否有信號要處理,若是要調用 do_signal

時鐘中斷的下半部分
在上面許多的外部中斷中,有一個特殊的中斷的處理 timer_interrupt, 它的下半部分主要處理:時間計算和校準定時器工做

所以,咱們有了下面的工做

*下半部分(包括softirq, tasklet, bottom_half )
*後續處理的流程
*時鐘中斷的下半部分
*定時器

[目錄]


內存

 

[目錄]


用戶態


    用戶空間存取內核空間,具體的實現方法要從兩個方面考慮,先是用戶進程,須要調用mmapp
來將本身的一段虛擬空間映射到內核態分配的物理內存;而後內核空間須要從新設置用戶進程的
這段虛擬內存的頁表,使它的物理地址指向對應的物理內存。針對linux內核的幾種不一樣的內存分
配方式(kmalloc、vmalloc和ioremap),須要進行不一樣的處理。關於這個話題,前面已有文章述
了,<<Linxu設備驅動程序>>也專門用一章的內容來說述,它們所用的方法是徹底同樣的。這裏只
是重複說一遍,以溫故而知新。

1、Linux內存管理概述

這裏說一下個人理解,主要從數據結構說。

一、物理內存都是按順序分紅一頁一頁的,每頁用一個page結構來描述。系統全部的物理頁 面的page結

構描述就組成了一個數組mem_map。

二、進程的虛擬地址空間用task_struct的域mm來描述,它是一個mm_struct結構,這個結構包包含了指向?

程頁目錄的指針(pgd_t * pgd)和指向進程虛擬內存區域的指針(struct vm_area_structt * mmap)

三、進程虛擬內存區域具備相同屬性的段用結構vm_area_struct描述(簡稱爲VMA)。進程所全部的VMA?


樹組織。

四、每一個VMA就是一個對象,定義了一組操做,能夠經過這組操做來對不一樣類型的VMA進行不屯 的處理。

例如對vmalloc分配的內存的映射就是經過其中的nopage操做實現的。

2、mmap處理過程

當用戶調用mmap的時候,內核進行以下的處理:

一、先在進程的虛擬空間查找一塊VMA;

二、將這塊VMA去映射

三、若是設備驅動程序或者文件系統的file_operations定義了mmap操做,則調用它

四、將這個VMA插入到進程的VMA鏈中

file_operations的中定義的mmap方法原型以下:
int (*mmap) (struct file *, struct vm_area_struct *);

其中file是虛擬空間映射到的文件結構,vm_area_struct就是步驟1中找到的VMA。

3、缺頁故障處理過程

當訪問一個無效的虛擬地址(多是保護故障,也可能缺頁故障等)的時候,就會產生一個個頁故障,?

統的處理過程以下:

一、找到這個虛擬地址所在的VMA;

二、若是必要,分配中間頁目錄表和頁表

三、若是頁表項對應的物理頁面不存在,則調用這個VMA的nopage方法,它返回物理頁面的paage描述結構

(固然這只是其中的一種狀況)

四、針對上面的狀況,將物理頁面的地址填充到頁表中

當頁故障處理完後,系統將從新啓動引發故障的指令,而後就能夠正常訪問了

下面是VMA的方法:
struct vm_operations_struct {
void (*open)(struct vm_area_struct * area);
void (*close)(struct vm_area_struct * area);
struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, innt

write_access);
};

其中缺頁函數nopage的address是引發缺頁故障的虛擬地址,area是它所在的VMA,write_acccess是存取

屬性。

3、具體實現

3.一、對kmalloc分配的內存的映射

對kmalloc分配的內存,由於是一段連續的物理內存,因此它能夠簡單的在mmap例程中設置漢 頁表的物

理地址,方法是使用函數remap_page_range。它的原型以下:

int remap_page_range(unsigned long from, unsigned long phys_addr, unsigned long size,

pgprot_t prot)

其中from是映射開始的虛擬地址。這個函數爲虛擬地址空間from和from+size之間的範圍構栽 頁表;

phys_addr是虛擬地址應該映射到的物理地址;size是被映射區域的大小;prot是保護標誌?

remap_page_range的處理過程是對from到form+size之間的每個頁面,查找它所在的頁目侶己 頁表(

必要時創建頁表),清除頁表項舊的內容,從新填寫它的物理地址與保護域。

remap_page_range能夠對多個連續的物理頁面進行處理。<<Linux設備驅動程序>>指出,

remap_page_range只能給予對保留的頁和物理內存之上的物理地址的訪問,當對非保留的頁頁使?

remap_page_range時,缺省的nopage處理控制映射被訪問的虛地址處的零頁。因此在分配內內存後,就?

對所分配的內存置保留位,它是經過函數mem_map_reserve實現的,它就是對相應物理頁面?

PG_reserved標誌位。(關於這一點,參見前面的主題爲「關於remap_page_range的疑問」檔奶致郟?

由於remap_page_range有上面的限制,因此能夠用另一種方式,就是採用和vmalloc分配檔哪 存一樣

的方法,對缺頁故障進行處理。

3.二、對vmalloc分配的內存的映射


3.2.一、vmalloc分配內存的過程

(1)、進行預處理和合法性檢查,例如將分配長度進行頁面對齊,檢查分配長度是否過大?

(2)、以GFP_KERNEL爲優先級調用kmalloc分配(GFP_KERNEL用在進程上下文中,因此這裏裏就限制了?

中斷處理程序中調用vmalloc)描述vmalloc分配的內存的vm_struct結構。

(3)、將size加一個頁面的長度,使中間造成4K的隔離帶,而後在VMALLOC_START和VMALLOOC_END之間

編歷vmlist鏈表,尋找一段自由內存區間,將其地址填入vm_struct結構中

(4)、返回這個地址

vmalloc分配的物理內存並不連續

3.2.二、頁目錄與頁表的定義

typedef struct { unsigned long pte_low; } pte_t;
typedef struct { unsigned long pmd; } pmd_t;
typedef struct { unsigned long pgd; } pgd_t;
#define pte_val(x) ((x).pte_low)

3.2.三、常見例程:

(1)、virt_to_phys():內核虛擬地址轉化爲物理地址
#define __pa(x)  ((unsigned long)(x)-PAGE_OFFSET)
extern inline unsigned long virt_to_phys(volatile void * address)
{
return __pa(address);
}

上面轉換過程是將虛擬地址減去3G(PAGE_OFFSET=0XC000000),由於內核空間從3G到3G+實實際內存一?

映射到物理地址的0到實際內存

(2)、phys_to_virt():內核物理地址轉化爲虛擬地址
#define __va(x)  ((void *)((unsigned long)(x)+PAGE_OFFSET))
extern inline void * phys_to_virt(unsigned long address)
{
return __va(address);
}
virt_to_phys()和phys_to_virt()都定義在include/asm-i386/io.h中

(3)、#define virt_to_page(kaddr) (mem_map + (__pa(kaddr) >> PAGE_SHIFT))(內核核2.4?
   #define VALID_PAGE(page) ((page - mem_map) < max_mapnr)(內核2.4)
第一個宏根據虛擬地址,將其轉換爲相應的物理頁面的page描述結構,第二個宏判斷頁面是是否是在有?

的物理頁面內。(這兩個宏處理的虛擬地址必須是內核虛擬地址,例如kmalloc返回的地址#雜?

vmalloc返回的地址並不能這樣,由於vmalloc分配的並非連續的物理內存,中間可能有空空洞?

3.2.四、vmalloc分配的內存的mmap的實現:

對vmalloc分配的內存須要經過設置相應VMA的nopage方法來實現,當產生缺頁故障的時候,,會調用VM

的nopage方法,咱們的目的就是在nopage方法中返回一個page結構的指針,爲此,須要經過過以下步驟?

(1) pgd_offset_k或者 pgd_offset:查找虛擬地址所在的頁目錄表,前者對應內核空間檔男 擬地址

,後者對應用戶空間的虛擬地址
#define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address))
#define pgd_offset_k(address) pgd_offset(&init_mm, address)
對於後者,init_mm是進程0(idle process)的虛擬內存mm_struct結構,全部進程的內核 頁表都同樣

。在vmalloc分配內存的時候,要刷新內核頁目錄表,2.4中爲了節省開銷,只更改了進程0檔哪 核頁目

錄,而對其它進程則經過訪問時產生頁面異常來進行更新各自的內核頁目錄

(2)pmd_offset:找到虛擬地址所在的中間頁目錄項。在查找以前應該使用pgd_none判斷適 否存在相

應的頁目錄項,這些函數以下:
extern inline int pgd_none(pgd_t pgd)  { return 0; }
extern inline pmd_t * pmd_offset(pgd_t * dir, unsigned long address)
{
return (pmd_t *) dir;
}

(3)pte_offset:找到虛擬地址對應的頁表項。一樣應該使用pmd_none判斷是否存在相應檔 中間頁目

錄:
#define pmd_val(x) ((x).pmd)
#define pmd_none(x) (!pmd_val(x))
#define __pte_offset(address) /
  ((address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
#define pmd_page(pmd) /
  ((unsigned long) __va(pmd_val(pmd) & PAGE_MASK))
#define pte_offset(dir, address) ((pte_t *) pmd_page(*(dir)) + /
  __pte_offset(address))

(4)pte_present和pte_page:前者判斷頁表對應的物理地址是否有效,後者取出頁表中物物理地址對?

的page描述結構
#define pte_present(x) ((x).pte_low & (_PAGE_PRESENT | _PAGE_PROTNONE))
#define pte_page(x) (mem_map+((unsigned long)(((x).pte_low >> PAGE_SHIFT))))
#define page_address(page) ((page)->virtual)

 


下面的一個DEMO與上面的關係不大,它是作這樣一件事情,就是在啓動的時候保留一段內存存,而後使?

ioremap將它映射到內核虛擬空間,同時又用remap_page_range映射到用戶虛擬空間,這樣亮 邊都能訪

問,經過內核虛擬地址將這段內存初始化串"abcd",而後使用用戶虛擬地址讀出來。

/************mmap_ioremap.c**************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/wrapper.h>  /* for mem_map_(un)reserve */
#include <asm/io.h>          /* for virt_to_phys */
#include <linux/slab.h>   /* for kmalloc and kfree */

MODULE_PARM(mem_start,"i");
MODULE_PARM(mem_size,"i");

static int mem_start=101,mem_size=10;
static char * reserve_virt_addr;
static int major;

int mmapdrv_open(struct inode *inode, struct file *file);
int mmapdrv_release(struct inode *inode, struct file *file);
int mmapdrv_mmap(struct file *file, struct vm_area_struct *vma);

static struct file_operations mmapdrv_fops =
{
  owner:   THIS_MODULE,
  mmap:    mmapdrv_mmap,
  open:    mmapdrv_open,
  release: mmapdrv_release,
};


int init_module(void)
{
  if ( ( major = register_chrdev(0, "mmapdrv", &mmapdrv_fops) ) < 0 )
    {
      printk("mmapdrv: unable to register character device/n");
      return (-EIO);
    }
  printk("mmap device major = %d/n",major );

  printk( "high memory physical address 0x%ldM/n",
   virt_to_phys(high_memory)/1024/1024 );

  reserve_virt_addr = ioremap( mem_start*1024*1024,mem_size*1024*1024);
  printk( "reserve_virt_addr = 0x%lx/n", (unsigned long)reserve_virt_addr );
  if ( reserve_virt_addr )
    {
      int i;
      for ( i=0;i<mem_size*1024*1024;i+=4)
{
   reserve_virt_addr[i] = 'a';
   reserve_virt_addr[i+1] = 'b';
   reserve_virt_addr[i+2] = 'c';
   reserve_virt_addr[i+3] = 'd';
}
    }
  else
    {
      unregister_chrdev( major, "mmapdrv" );
      return -ENODEV;
    }

  return 0;
}

/* remove the module */
void cleanup_module(void)
{
  if ( reserve_virt_addr )
    iounmap( reserve_virt_addr );

  unregister_chrdev( major, "mmapdrv" );

  return;
}

int mmapdrv_open(struct inode *inode, struct file *file)
{
  MOD_INC_USE_COUNT;
  return(0);
}

int mmapdrv_release(struct inode *inode, struct file *file)
{
  MOD_DEC_USE_COUNT;
  return(0);
}

int mmapdrv_mmap(struct file *file, struct vm_area_struct *vma)
{
  unsigned long offset = vma->vm_pgoff<<PAGE_SHIFT;
  unsigned long size = vma->vm_end - vma->vm_start;

  if ( size > mem_size*1024*1024 )
    {
      printk("size too big/n");
      return(-ENXIO);
    }

  offset = offset + mem_start*1024*1024;

  /* we do not want to have this area swapped out, lock it */
  vma->vm_flags |= VM_LOCKED;
  if ( remap_page_range(vma->vm_start,offset,size,PAGE_SHARED))
    {
      printk("remap page range failed/n");
      return -ENXIO;
    }

  return(0);
}


使用LDD2源碼裏面自帶的工具mapper測試結果以下:

[root@localhost modprg]# insmod mmap_ioremap.mod
mmap device major = 254
high memory physical address 0x100M
reserve_virt_addr = 0xc7038000

[root@localhost modprg]# mknod mmapdrv c 254 0

[root@localhost modprg]# ./mapper mmapdrv 0 1024 | od -Ax -t x1
mapped "mmapdrv" from 0 to 1024
000000 61 62 63 64 61 62 63 64 61 62 63 64 61 62 63 64
*
000400

[root@localhost modprg]#

 

[目錄]


內核頁目錄的初始化


內核頁目錄的初始化

內核頁目錄的初始化

/* swapper_pg_dir is the main page directory, address 0x00101000*/

>>> 內核頁目錄,第0,1項和第76八、767項均爲映射到物理內存0-8M的頁目錄項
>>> 其頁表的物理地址是0x00102000和0x00103000,即下面的pg0和pg1所在的位置
>>> (在啓動的時候,將內核映像移到0x0010000處)。
>>> 之因此第0,1項與第768和767相同,是由於在開啓分頁前的線性地址0-8M和開啓
>>> 分頁以後的3G-3G+8M均映射到相同的物理地址0-8M

/*
* This is initialized to create an identity-mapping at 0-8M (for bootup
* purposes) and another mapping of the 0-8M area at virtual address
* PAGE_OFFSET.
*/
.org 0x1000
ENTRY(swapper_pg_dir)
.long 0x00102007
.long 0x00103007
.fill BOOT_USER_PGD_PTRS-2,4,0
/* default: 766 entries */
.long 0x00102007
.long 0x00103007
/* default: 254 entries */
.fill BOOT_KERNEL_PGD_PTRS-2,4,0

/*
* The page tables are initialized to only 8MB here - the final page
* tables are set up later depending on memory size.
*/
>>> 下面爲物理地址0-8M的頁表項
>>> 從0x4000到0x2000共2k個頁表項,映射0-8M的物理內存

.org 0x2000
ENTRY(pg0)

.org 0x3000
ENTRY(pg1)

/*
* empty_zero_page must immediately follow the page tables ! (The
* initialization loop counts until empty_zero_page)
*/

.org 0x4000
ENTRY(empty_zero_page)

>>> 進程0的頁目錄指向swapper_pg_dir
#define INIT_MM(name) /
{        /
mmap:  &init_mmap,    /
mmap_avl: NULL,     /
mmap_cache: NULL,     /
pgd:  swapper_pg_dir,   /
mm_users: ATOMIC_INIT(2),   /
mm_count: ATOMIC_INIT(1),   /
map_count: 1,     /
mmap_sem: __RWSEM_INITIALIZER(name.mmap_sem), /
page_table_lock: SPIN_LOCK_UNLOCKED,   /
mmlist:  LIST_HEAD_INIT(name.mmlist), /
}

/*
* paging_init() sets up the page tables - note that the first 8MB are
* already mapped by head.S.
*
* This routines also unmaps the page at virtual kernel address 0, so
* that we can trap those pesky NULL-reference errors in the kernel.
*/
void __init paging_init(void)
{
pagetable_init();

__asm__( "movl %%ecx,%%cr3/n" ::"c"(__pa(swapper_pg_dir)));

。。。。。。。。。。。
}


static void __init pagetable_init (void)
{
unsigned long vaddr, end;
pgd_t *pgd, *pgd_base;
int i, j, k;
pmd_t *pmd;
pte_t *pte, *pte_base;

>>> end虛擬空間的最大值(最大物理內存+3G)
/*
  * This can be zero as well - no problem, in that case we exit
  * the loops anyway due to the PTRS_PER_* conditions.
  */
end = (unsigned long)__va(max_low_pfn*PAGE_SIZE);

pgd_base = swapper_pg_dir;
#if CONFIG_X86_PAE
for (i = 0; i < PTRS_PER_PGD; i++)
  set_pgd(pgd_base + i, __pgd(1 + __pa(empty_zero_page)));
#endif
>>> 內核起始虛擬空間在內核頁目錄表中的索引
i = __pgd_offset(PAGE_OFFSET);
pgd = pgd_base + i;

>>> #define PTRS_PER_PGD 1024
>>> 對頁目錄的從768項開始的每一項
for (; i < PTRS_PER_PGD; pgd++, i++) {
>>> vaddr爲第i項頁目錄項所映射的內核空間的起始虛擬地址,PGDIR_SIZE=4M
  vaddr = i*PGDIR_SIZE;
  if (end && (vaddr >= end))
   break;
#if CONFIG_X86_PAE
  pmd = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);
  set_pgd(pgd, __pgd(__pa(pmd) + 0x1));
#else
>>> 對兩級映射機制,pmd其實是pgd
  pmd = (pmd_t *)pgd;
#endif
  if (pmd != pmd_offset(pgd, 0))
   BUG();

  for (j = 0; j < PTRS_PER_PMD; pmd++, j++) {
   vaddr = i*PGDIR_SIZE + j*PMD_SIZE;
   if (end && (vaddr >= end))
    break;
>>> 假如內核不支持 Page Size Extensions
   if (cpu_has_pse) {
   。。。。。。。。。。
   }
>>> 分配內核頁表
   pte_base = pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
>>> 對每一項頁表項
   for (k = 0; k < PTRS_PER_PTE; pte++, k++) {
    vaddr = i*PGDIR_SIZE + j*PMD_SIZE + k*PAGE_SIZE;
    if (end && (vaddr >= end))
     break;
>>> 將頁面的物理地址填入頁表項中
    *pte = mk_pte_phys(__pa(vaddr), PAGE_KERNEL);
   }
>>> 將頁表的物理地址填入到頁目錄項中
   set_pmd(pmd, __pmd(_KERNPG_TABLE + __pa(pte_base)));
   if (pte_base != pte_offset(pmd, 0))
    BUG();

  }
}

/*
  * Fixed mappings, only the page table structure has to be
  * created - mappings will be set by set_fixmap():

  */
vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
fixrange_init(vaddr, 0, pgd_base);

#if CONFIG_HIGHMEM
。。。。。。。。。。。。
#endif

#if CONFIG_X86_PAE
。。。。。。。。。。。。
#endif
}

[目錄]


內核線程頁目錄的借用


建立內核線程的時候,因爲內核線程沒有用戶空間,而全部進程的內核頁目錄都是同樣的((某些狀況下可能有不一樣步的狀況出現,主要是爲了減輕同步全部進程內核頁目錄的開銷,而只是在各個進程要訪問內核空間,若是有不一樣步的狀況,而後才進?
同步處理),因此建立的內核線程的
內核頁目錄老是借用進程0的內核頁目錄。

>>> kernel_thread以標誌CLONE_VM調用clone系統調用
/*
* Create a kernel thread
*/
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
long retval, d0;

__asm__ __volatile__(
  "movl %%esp,%%esi/n/t"
  "int $0x80/n/t"  /* Linux/i386 system call */
  "cmpl %%esp,%%esi/n/t" /* child or parent? */
  /* Load the argument into eax, and push it.  That way, it does
   * not matter whether the called function is compiled with
   * -mregparm or not.  */
  "movl %4,%%eax/n/t"
  "pushl %%eax/n/t"
  "call *%5/n/t"  /* call fn */
  "movl %3,%0/n/t" /* exit */
  "int $0x80/n"
  "1:/t"
  :"=&a" (retval), "=&S" (d0)
  :"0" (__NR_clone), "i" (__NR_exit),
   "r" (arg), "r" (fn),
   "b" (flags | CLONE_VM)
  : "memory");
return retval;
}

>>> sys_clone->do_fork->copy_mm:
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{
struct mm_struct * mm, *oldmm;
int retval;

。。。。。。。。

tsk->mm = NULL;
tsk->active_mm = NULL;

/*
  * Are we cloning a kernel thread?
  *
  * We need to steal a active VM for that..
  */
>>> 若是是內核線程的子線程(mm=NULL),則直接退出,此時內核線程mm和active_mm均爲爲NULL
oldmm = current->mm;
if (!oldmm)
  return 0;

>>> 內核線程,只是增長當前進程的虛擬空間的引用計數
if (clone_flags & CLONE_VM) {
  atomic_inc(&oldmm->mm_users);
  mm = oldmm;
  goto good_mm;
}

。。。。。。。。。。

good_mm:
>>> 內核線程的mm和active_mm指向當前進程的mm_struct結構
tsk->mm = mm;
tsk->active_mm = mm;
return 0;

。。。。。。。
}

而後內核線程通常調用daemonize來釋放對用戶空間的引用:
>>> daemonize->exit_mm->_exit_mm:
/*
* Turn us into a lazy TLB process if we
* aren't already..
*/
static inline void __exit_mm(struct task_struct * tsk)
{
struct mm_struct * mm = tsk->mm;

mm_release();
if (mm) {
  atomic_inc(&mm->mm_count);
  if (mm != tsk->active_mm) BUG();
  /* more a memory barrier than a real lock */
  task_lock(tsk);
>>> 釋放用戶虛擬空間的數據結構
  tsk->mm = NULL;
  task_unlock(tsk);
  enter_lazy_tlb(mm, current, smp_processor_id());

>>> 遞減mm的引用計數並是否爲0,是則釋放mm所表明的映射
  mmput(mm);
}
}

asmlinkage void schedule(void)
{
。。。。。。。。。
if (!current->active_mm) BUG();

。。。。。。。。。

prepare_to_switch();
{
  struct mm_struct *mm = next->mm;
  struct mm_struct *oldmm = prev->active_mm;
>>> mm = NULL,選中的爲內核線程
  if (!mm) {
>>> 對內核線程,active_mm = NULL,不然必定是出錯了
   if (next->active_mm) BUG();
>>> 選中的內核線程active_mm借用老進程的active_mm
   next->active_mm = oldmm;
   atomic_inc(&oldmm->mm_count);
   enter_lazy_tlb(oldmm, next, this_cpu);
  } else {
>>> mm != NULL 選中的爲用戶進程,active_mm必須與mm相等,不然必定是出錯了
   if (next->active_mm != mm) BUG();
   switch_mm(oldmm, mm, next, this_cpu);
  }

>>> prev = NULL ,切換出去的是內核線程
  if (!prev->mm) {
>>> 設置其 active_mm = NULL 。
   prev->active_mm = NULL;
   mmdrop(oldmm);
  }
}

}

對內核線程的虛擬空間總結一下:
1、建立的時候:
父進程是用戶進程,則mm和active_mm均共享父進程的,而後內核線程通常調用daemonize適頭舖m
父進程是內核線程,則mm和active_mm均爲NULL
總之,內核線程的mm = NULL;進程調度的時候以此爲依據判斷是用戶進程仍是內核線程。

2、進程調度的時候
若是切換進來的是內核線程,則置active_mm爲切換出去的進程的active_mm;
若是切換出去的是內核線程,則置active_mm爲NULL。

[目錄]


用戶進程內核頁目錄的創建


用戶進程內核頁目錄的創建

在fork一個進程的時候,必須創建進程本身的內核頁目錄項(內核頁目錄項要
與用戶空間的的頁目錄放在同一個物理地址連續的頁面上,因此不能共享,但
全部進程的內核頁表與進程0共享?


3G用戶,頁目錄中一項映射4M的空間(一項頁目錄1024項頁表,每項頁表對應1個頁面4K)# 即:
#define PGDIR_SHIFT 22
#define PGDIR_SIZE (1UL << PGDIR_SHIFT)

>>> sys_fork->do_fork->copy_mm->mm_init->pgd_alloc->get_pgd_slow

#if CONFIG_X86_PAE

。。。。。。。。。。。。。

#else

extern __inline__ pgd_t *get_pgd_slow(void)
{
>>> 分配頁目錄表(包含1024項頁目錄),即爲一個進程分配的頁目錄能夠映射的空間爲10024*4M=4G
pgd_t *pgd = (pgd_t *)__get_free_page(GFP_KERNEL);

if (pgd) {
>>> #define USER_PTRS_PER_PGD (TASK_SIZE/PGDIR_SIZE)
>>> TASK_SIZE爲3G大小,USER_PTRS_PER_PGD爲用戶空間對應的頁目錄項數目(3G/4M=768?
>>> 將用戶空間的頁目錄項清空
  memset(pgd, 0, USER_PTRS_PER_PGD * sizeof(pgd_t));
>>> 將內核頁目錄表(swapper_pg_dir)的第768項到1023項拷貝到進程的頁目錄表的第7688項到1023項中
  memcpy(pgd + USER_PTRS_PER_PGD, swapper_pg_dir + USER_PTRS_PER_PGD, (PTRS_PER__PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t));
}
return pgd;
}

#endif

[目錄]


內核頁目錄的同步


內核頁目錄的同步

當一個進程在內核空間發生缺頁故障的時候,在其處理程序中,就要經過0號進程的頁目錄覽 同步本進程的內核頁目錄,實際上就是拷貝0號進程的內核頁目錄到本進程中(內核頁表與進程0共享,故不須要複製)。以下:
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
。。。。。。。。
>>> 缺頁故障產生的地址
/* get the address */
__asm__("movl %%cr2,%0":"=r" (address));

tsk = current;

/*
  * We fault-in kernel-space virtual memory on-demand. The
  * 'reference' page table is init_mm.pgd.
  */
>>> 若是缺頁故障在內核空間
if (address >= TASK_SIZE)
  goto vmalloc_fault;

。。。。。。。。。

vmalloc_fault:
{
  /*
   * Synchronize this task's top level page-table
   * with the 'reference' page table.
   */
  int offset = __pgd_offset(address);
  pgd_t *pgd, *pgd_k;
  pmd_t *pmd, *pmd_k;

  pgd = tsk->active_mm->pgd + offset;
  pgd_k = init_mm.pgd + offset;

>>> /*
>>>  * (pmds are folded into pgds so this doesnt get actually called,
>>>  * but the define is needed for a generic inline function.)
>>>  */
>>> #define set_pmd(pmdptr, pmdval) (*(pmdptr) = pmdval)
>>> #define set_pgd(pgdptr, pgdval) (*(pgdptr) = pgdval)

>>> 若是本進程的該地址的內核頁目錄不存在
  if (!pgd_present(*pgd)) {
>>> 若是進程0的該地址處的內核頁目錄也不存在,則出錯
   if (!pgd_present(*pgd_k))
    goto bad_area_nosemaphore;
>>> 複製進程0的該地址的內核頁目錄到本進程的相應頁目錄中
   set_pgd(pgd, *pgd_k);
   return;
  }
>>> extern inline pmd_t * pmd_offset(pgd_t * dir, unsigned long address)
>>> {
>>>  return (pmd_t *) dir;
>>> }
  pmd = pmd_offset(pgd, address);
  pmd_k = pmd_offset(pgd_k, address);

>>> 對中間頁目錄,若是是兩級頁表,下面的幾步操做與上面的重複
  if (pmd_present(*pmd) || !pmd_present(*pmd_k))
   goto bad_area_nosemaphore;
  set_pmd(pmd, *pmd_k);
  return;
}


/*
* Switch to real mode and then execute the code
* specified by the code and length parameters.
* We assume that length will aways be less that 100!
*/
void machine_real_restart(unsigned char *code, int length)
{

。。。。。。。。。。。。。

/* Remap the kernel at virtual address zero, as well as offset zero
    from the kernel segment.  This assumes the kernel segment starts at
    virtual address PAGE_OFFSET. */

memcpy (swapper_pg_dir, swapper_pg_dir + USER_PGD_PTRS,
  sizeof (swapper_pg_dir [0]) * KERNEL_PGD_PTRS);


/* Make sure the first page is mapped to the start of physical memory.
    It is normally not mapped, to trap kernel NULL pointer dereferences. */

pg0[0] = _PAGE_RW | _PAGE_PRESENT;

/*
  * Use `swapper_pg_dir' as our page directory.
  */
asm volatile("movl %0,%%cr3": :"r" (__pa(swapper_pg_dir)));

[目錄]


內存管理 from aka


Linux 內存管理子系統導讀

本文主要針對2.4的kernel。

關於本文的組織:

個人目標是‘導讀’,提供linux內存管理子系統的總體概念,同時給出進一步深刻研究某個部分時的輔助信息(包括代碼組織,文件和主要函數的意義和一些參考文檔)。之因此採起這種方式,是由於我本人在閱讀代碼的過程當中,深感「讀懂一段代碼容易,把握總體思想卻極不容易」。並且,在我寫一些內核代碼時,也以爲不少狀況下,不必定非得很具體地理解全部內核代碼,每每瞭解它的接口和總體工做原理就夠了。固然,我我的的能力有限,時間也很不夠,不少東西也是近期迫於講座壓力臨時學的:),內容不免偏頗甚至錯誤,歡迎你們指正。

 

存儲層次結構和x86存儲管理硬件(MMU)

這裏假定你們對虛擬存儲,段頁機制有必定的瞭解。主要強調一些很重要的或者容易誤解的概念。


存儲層次

高速緩存(cache) --〉 主存(main memory) ---〉 磁盤(disk)

理解存儲層次結構的根源:CPU速度和存儲器速度的差距。

層次結構可行的緣由:局部性原理。

LINUX的任務:


減少footprint,提升cache命中率,充分利用局部性。


實現虛擬存儲以知足進程的需求,有效地管理內存分配,力求最合理地利用有限的資源。

參考文檔:

《too little,too small》by Rik Van Riel, Nov. 27,2000.

以及全部的體系結構教材:)


MMU的做用

輔助操做系統進行內存管理,提供虛實地址轉換等硬件支持。


x86的地址

邏輯地址: 出如今機器指令中,用來制定操做數的地址。段:偏移

線性地址:邏輯地址通過分段單元處理後獲得線性地址,這是一個32位的無符號整數,可用於定位4G個存儲單元。

物理地址:線性地址通過頁表查找後得出物理地址,這個地址將被送到地址總線上指示所要訪問的物理內存單元。


LINUX: 儘可能避免使用段功能以提升可移植性。如經過使用基址爲0的段,使邏輯地址==線性地址。


x86的段

保護模式下的段:選擇子+描述符。不只僅是一個基地址的緣由是爲了提供更多的信息:保護、長度限制、類型等。描述符存放在一張表中(GDT或LDT),選擇子能夠認爲是表的索引。段寄存器中存放的是選擇子,在段寄存器裝入的同時,描述符中的數據被裝入一個不可見的寄存器以便cpu快速訪問。(圖)P40

專用寄存器:GDTR(包含全局描述附表的首地址),LDTR(當前進程的段描述附表首地址),TSR(指向當前進程的任務狀態段)


LINUX使用的段:

__KERNEL_CS: 內核代碼段。範圍 0-4G。可讀、執行。DPL=0。

__KERNEL_DS:內核代碼段。範圍 0-4G。可讀、寫。DPL=0。

__USER_CS:內核代碼段。範圍 0-4G。可讀、執行。DPL=3。

__USER_DS:內核代碼段。範圍 0-4G。可讀、寫。DPL=3。

TSS(任務狀態段):存儲進程的硬件上下文,進程切換時使用。(由於x86硬件對TSS有必定支持,全部有這個特殊的段和相應的專用寄存器。)

default_ldt:理論上每一個進程均可以同時使用不少段,這些段能夠存儲在本身的ldt段中,但實際linux極少利用x86的這些功能,多數狀況下全部進程共享這個段,它只包含一個空描述符。

還有一些特殊的段用在電源管理等代碼中。

(在2.2之前,每一個進程的ldt和TSS段都存在GDT中,而GDT最多隻能有8192項,所以整個系統的進程總數被限制在4090左右。2。4裏再也不把它們存在GDT中,從而取消了這個限制。)

__USER_CS和__USER_DS段都是被全部在用戶態下的進程共享的。注意不要把這個共享和進程空間的共享混淆:雖然你們使用同一個段,但經過使用不一樣的頁表由分頁機制保證了進程空間仍然是獨立的。

 

x86的分頁機制

x86硬件支持兩級頁表,奔騰pro以上的型號還支持Physical address Extension Mode和三級頁表。所謂的硬件支持包括一些特殊寄存器(cr0-cr4)、以及CPU可以識別頁表項中的一些標誌位並根據訪問狀況作出反應等等。如讀寫Present位爲0的頁或者寫Read/Write位爲0的頁將引發CPU發出page fault異常,訪問完頁面後自動設置accessed位等。


linux採用的是一個體繫結構無關的三級頁表模型(如圖),使用一系列的宏來掩蓋各類平臺的細節。例如,經過把PMD看做只有一項的表並存儲在pgd表項中(一般pgd表項中存放的應該是pmd表的首地址),頁表的中間目錄(pmd)被巧妙地‘摺疊’到頁表的全局目錄(pgd),從而適應了二級頁表硬件。


6. TLB

TLB全稱是Translation Look-aside Buffer,用來加速頁表查找。這裏關鍵的一點是:若是操做系統更改了頁表內容,它必須相應的刷新TLB以使CPU不誤用過期的表項。


7. Cache

Cache 基本上是對程序員透明的,可是不一樣的使用方法能夠致使大不相同的性能。linux有許多關鍵的地方對代碼作了精心優化,其中不少就是爲了減小對cache沒必要要的污染。如把只有出錯狀況下用到的代碼放到.fixup section,把頻繁同時使用的數據集中到一個cache行(如struct task_struct),減小一些函數的footprint,在slab分配器裏頭的slab coloring等。

另外,咱們也必須知道何時cache要無效:新map/remap一頁到某個地址、頁面換出、頁保護改變、進程切換等,也即當cache對應的那個地址的內容或含義有所變化時。固然,不少狀況下不須要無效整個cache,只須要無效某個地址或地址範圍便可。實際上,

intel在這方面作得很是好用,cache的一致性徹底由硬件維護。

 

關於x86處理器更多信息,請參照其手冊:Volume 3: Architecture and Programming Manual,能夠從ftp://download.intel.com/design/pentium/MANUALS/24143004.pdf得到


8. Linux 相關實現

這一部分的代碼和體系結構緊密相關,所以大多位於arch子目錄下,並且大量以宏定義和inline函數形式存在於頭文件中。以i386平臺爲例,主要的文件包括:


page.h

頁大小、頁掩碼定義。PAGE_SIZE,PAGE_SHIFT和PAGE_MASK。

對頁的操做,如清除頁內容clear_page、拷貝頁copy_page、頁對齊page_align

還有內核虛地址的起始點:著名的PAGE_OFFSET:)和相關的內核中虛實地址轉換的宏__pa和__va.。

virt_to_page從一個內核虛地址獲得該頁的描述結構struct page *.咱們知道,全部物理內存都由一個memmap數組來描述。這個宏就是計算給定地址的物理頁在這個數組中的位置。另外這個文件也定義了一個簡單的宏檢查一個頁是否是合法:VALID_PAGE(page)。若是page離memmap數組的開始太遠以致於超過了最大物理頁面應有的距離則是不合法的。

比較奇怪的是頁表項的定義也放在這裏。pgd_t,pmd_t,pte_t和存取它們值的宏xxx_val

 

pgtable.h pgtable-2level.h pgtable-3level.h

顧名思義,這些文件就是處理頁表的,它們提供了一系列的宏來操做頁表。pgtable-2level.h和pgtable-2level.h則分別對應x86二級、三級頁表的需求。首先固然是表示每級頁表有多少項的定義不一樣了。並且在PAE模式下,地址超過32位,頁表項pte_t用64位來表示(pmd_t,pgd_t不須要變),一些對整個頁表項的操做也就不一樣。共有以下幾類:

[pte/pmd/pgd]_ERROR 出措時要打印項的取值,64位和32位固然不同。

set_[pte/pmd/pgd] 設置表項值

pte_same 比較 pte_page 從pte得出所在的memmap位置

pte_none 是否爲空。

__mk_pte 構造pte

pgtable.h的宏太多,再也不一一解釋。實際上也比較直觀,一般從名字就能夠看出宏的意義來了。pte_xxx宏的參數是pte_t,而ptep_xxx的參數是pte_t *。2.4 kernel在代碼的clean up方面仍是做了一些努力,很多地方含糊的名字變明確了,有些函數的可讀性頁變好了。

pgtable.h裏除了頁表操做的宏外,還有cache和tlb刷新操做,這也比較合理,由於他們經常是在頁表操做時使用。這裏的tlb操做是以__開始的,也就是說,內部使用的,真正對外接口在pgalloc.h中(這樣分開多是由於在SMP版本中,tlb的刷新函數和單機版本區別較大,有些再也不是內嵌函數和宏了)。


8.3 pgalloc.h

包括頁表項的分配和釋放宏/函數,值得注意的是表項高速緩存的使用:

pgd/pmd/pte_quicklist

內核中有許多地方使用相似的技巧來減小對內存分配函數的調用,加速頻繁使用的分配。如buffer cache中buffer_head和buffer,vm區域中最近使用的區域。

還有上面提到的tlb刷新的接口

8.4 segment.h

定義 __KERNEL_CS[DS] __USER_CS[DS]


參考:

《Understanding the Linux Kernel》的第二章給了一個對linux 的相關實現的簡要描述,

 

物理內存的管理。

2.4中內存管理有很大的變化。在物理頁面管理上實現了基於區的夥伴系統(zone based buddy system)。區(zone)的是根據內存的不一樣使用類型劃分的。對不一樣區的內存使用單獨的夥伴系統(buddy system)管理,並且獨立地監控空閒頁等。

(實際上更高一層還有numa支持。Numa(None Uniformed Memory Access)是一種體系結構,其中對系統裏的每一個處理器來講,不一樣的內存區域可能有不一樣的存取時間(通常是由內存和處理器的距離決定)。而通常的機器中內存叫作DRAM,即動態隨機存取存儲器,對每一個單元,CPU用起來是同樣快的。NUMA中訪問速度相同的一個內存區域稱爲一個Node,支持這種結構的主要任務就是要儘可能減小Node之間的通訊,使得每一個處理器要用到的數據儘量放在對它來講最快的Node中。2.4內核中node&#0;相應的數據結構是pg_data_t,每一個node擁有本身的memmap數組,把本身的內存分紅幾個zone,每一個zone再用獨立的夥伴系統管理物理頁面。Numa要對付的問題還有不少,也遠沒有完善,就很少說了)

一些重要的數據結構粗略地表示以下:

 


基於區的夥伴系統的設計&#0;物理頁面的管理

內存分配的兩大問題是:分配效率、碎片問題。一個好的分配器應該可以快速的知足各類大小的分配要求,同時不能產生大量的碎片浪費空間。夥伴系統是一個經常使用的比較好的算法。(解釋:TODO)

引入區的概念是爲了區份內存的不一樣使用類型(方法?),以便更有效地利用它們。

2.4有三個區:DMA, Normal, HighMem。前兩個在2.2實際上也是由獨立的buddy system管理的,但2.2中尚未明確的zone的概念。DMA區在x86體系結構中一般是小於16兆的物理內存區,由於DMA控制器只能使用這一段的內存。而HighMem是物理地址超過某個值(一般是約900M)的高端內存。其餘的是Normal區內存。因爲linux實現的緣由,高地址的內存不能直接被內核使用,若是選擇了CONFIG_HIGHMEM選項,內核會使用一種特殊的辦法來使用它們。(解釋:TODO)。HighMem只用於page cache和用戶進程。這樣分開以後,咱們將能夠更有針對性地使用內存,而不至於出現把DMA可用的內存大量給無關的用戶進程使用致使驅動程序無法獲得足夠的DMA內存等狀況。此外,每一個區都獨立地監控本區內存的使用狀況,分配時系統會判斷從哪一個區分配比較合算,綜合考慮用戶的要求和系統現狀。2.4裏分配頁面時可能會和高層的VM代碼交互(分配時根據空閒頁面的狀況,內核可能從夥伴系統裏分配頁面,也可能直接把已經分配的頁收回&#0;reclaim等),代碼比2.2複雜了很多,要全面地理解它得熟悉整個VM工做的機理。

整個分配器的主要接口是以下函數(mm.h page_alloc.c):


struct page * alloc_pages(int gfp_mask, unsigned long order) 根據gftp_mask的要求,從適當的區分配2^order個頁面,返回第一個頁的描述符。


#define alloc_page(gfp_mask) alloc_pages(gfp_mask,0)


unsigned long __get_free_pages((int gfp_mask, unsigned long order) 工做同alloc_pages,但返回首地址。


#define __get_free_page(gfp_mask) __get_free_pages(gfp_mask,0)


get_free_page 分配一個已清零的頁面。


__free_page(s) 和free_page(s)釋放頁面(一個/多個)前者以頁面描述符爲參數,後者以頁面地址爲參數。


關於Buddy算法,許多教科書上有詳細的描述,<Understanding the Linux Kernel>第六章對linux的實現有一個很好的介紹。關於zone base buddy更多的信息,能夠參見Rik Van Riel 寫的" design for a zone based memory allocator"。這我的是目前linuxmm的維護者,權威啦。這篇文章有一點過期了,98年寫的,當時尚未HighMem,但思想仍是有效的。還有,下面這篇文章分析2.4的實現代碼:

http://home.earthlink.net/~jknapka/linux-mm/zonealloc.html。


2. Slab--連續物理區域管理

單單分配頁面的分配器確定是不能知足要求的。內核中大量使用各類數據結構,大小從幾個字節到幾十上百k不等,都取整到2的冪次個頁面那是徹底不現實的。2.0的內核的解決方法是提供大小爲2,4,8,16,...,131056字節的內存區域。須要新的內存區域時,內核從夥伴系統申請頁面,把它們劃分紅一個個區域,取一個來知足需求;若是某個頁面中的內存區域都釋放了,頁面就交回到夥伴系統。這樣作的效率不高。有許多地方能夠改進:


不一樣的數據類型用不一樣的方法分配內存可能提升效率。好比須要初始化的數據結構,釋放後能夠暫存着,再分配時就沒必要初始化了。


內核的函數經常重複地使用同一類型的內存區,緩存最近釋放的對象能夠加速分配和釋放。


對內存的請求能夠按照請求頻率來分類,頻繁使用的類型使用專門的緩存,不多使用的能夠使用相似2.0中的取整到2的冪次的通用緩存。


使用2的冪次大小的內存區域時高速緩存衝突的機率較大,有可能經過仔細安排內存區域的起始地址來減小高速緩存衝突。


緩存必定數量的對象能夠減小對buddy系統的調用,從而節省時間並減小由此引發的高速緩存污染。

2.2實現的slab分配器體現了這些改進思想。

主要數據結構

接口:

kmem_cache_create/kmem_cache_destory

kmem_cache_grow/kmem_cache_reap 增加/縮減某類緩存的大小

kmem_cache_alloc/kmem_cache_free 從某類緩存分配/釋放一個對象

kmalloc/kfree 通用緩存的分配、釋放函數。

相關代碼(slab.c)。

相關參考:

http://www.lisoleg.net/lisoleg/memory/slab.pdf :Slab發明者的論文,必讀經典。

<Understanding the Linux Kernel> 第六章,具體實現的詳細清晰的描述。

AKA2000年的講座也有一些大蝦講過這個主題,請訪問aka主頁:www.aka.org.cn


3.vmalloc/vfree &#0;物理地址不連續,虛地址連續的內存管理

使用kernel頁表。文件vmalloc.c,相對簡單。


3、2.4內核的VM(完善中。。。)


進程地址空間管理

建立,銷燬。

mm_struct, vm_area_struct, mmap/mprotect/munmap

page fault處理,demand page, copy on write


相關文件:

include/linux/mm.h:struct page結構的定義,page的標誌位定義以及存取操做宏定義。struct vm_area_struct定義。mm子系統的函數原型說明。

include/linux/mman.h:和vm_area_struct的操做mmap/mprotect/munmap相關的常量宏定義。

memory.c:page fault處理,包括COW和demand page等。

對一個區域的頁表相關操做:

zeromap_page_range: 把一個範圍內的頁所有映射到zero_page

remap_page_range:給定範圍的頁從新映射到另外一塊地址空間。

zap_page_range:把給定範圍內的用戶頁釋放掉,頁表清零。

mlock.c: mlock/munlock系統調用。mlock把頁面鎖定在物理內存中。

mmap.c::mmap/munmap/brk系統調用。

mprotect.c: mprotect系統調用。

前面三個文件都大量涉及vm_area_struct的操做,有不少類似的xxx_fixup的代碼,它們的任務是修補受到影響的區域,保證vm_area_struct 鏈表正確。

 

交換

目的:


使得進程能夠使用更大的地址空間。


同時容納更多的進程。

任務:


選擇要換出的頁


決定怎樣在交換區中存儲頁面


決定何時換出


kswapd內核線程:每10秒激活一次

任務:當空閒頁面低於必定值時,從進程的地址空間、各種cache回收頁面

爲何不能等到內存分配失敗再用try_to_free_pages回收頁面?緣由:


有些內存分配時在中斷或異常處理調用,他們不能阻塞


有時候分配發生在某個關鍵路徑已經得到了一些關鍵資源的時候,所以它不能啓動IO。若是不巧這時全部的路徑上的內存分配都是這樣,內存就沒法釋放。

kreclaimd 從inactive_clean_list回收頁面,由__alloc_pages喚醒。

相關文件:

mm/swap.c kswapd使用的各類參數以及操做頁面年齡的函數。

mm/swap_file.c 交換分區/文件的操做。

mm/page_io.c 讀或寫一個交換頁。

mm/swap_state.c swap cache相關操做,加入/刪除/查找一個swap cache等。

mm/vmscan.c 掃描進程的vm_area,試圖換出一些頁面(kswapd)。

reclaim_page:從inactive_clean_list回收一個頁面,放到free_list

kclaimd被喚醒後重復調用reclaim_page直到每一個區的

zone->free_pages>= zone->pages_low

page_lauder:由__alloc_pages和try_to_free_pages等調用。一般是因爲freepages + inactive_clean_list的頁太少了。功能:把inactive_dirty_list的頁面轉移到inactive_clean_list,首先把已經被寫回文件或者交換區的頁面(by bdflush)放到inactive_clean_list,若是freepages確實短缺,喚醒bdflush,再循環一遍把必定數量的dirty頁寫回。

關於這幾個隊列(active_list,inactive_dirty_list,inactive_clean_list)的邏輯,請參照:文檔:RFC: design for new VM,能夠從lisoleg的文檔精華得到。

,


page cache、buffer cache和swap cache

page cache:讀寫文件時文件內容的cache,大小爲一個頁。不必定在磁盤上連續。

buffer cache:讀寫磁盤塊的時候磁盤塊內容的cache,buffer cache的內容對應磁盤上一個連續的區域,一個buffer cache大小可能從512(扇區大小)到一個頁。

swap cache: 是page cache的子集。用於多個進程共享的頁面被換出到交換區的狀況。

 

page cache 和 buffer cache的關係

本質上是很不一樣的,buffer cache緩衝磁盤塊內容,page cache緩衝文件的一頁內容。page cache寫回時會使用臨時的buffer cache來寫磁盤。


bdflush: 把dirty的buffer cache寫回磁盤。一般只當dirty的buffer太多或者須要更多的buffer而內存開始不足時運行。page_lauder也可能喚醒它。

kupdate: 定時運行,把寫回期限已經到了的dirty buffer寫回磁盤。


2.4的改進:page cache和buffer cache耦合得更好了。在2.2裏,磁盤文件的讀使用page cache,而寫繞過page cache,直接使用buffer cache,所以帶來了同步的問題:寫完以後必須使用update_vm_cache()更新可能有的page cache。2.4中page cache作了比較大的改進,文件能夠經過page cache直接寫了,page cache優先使用high memory。並且,2.4引入了新的對象:file address space,它包含用來讀寫一整頁數據的方法。這些方法考慮到了inode的更新、page cache處理和臨時buffer的使用。page cache和buffer cache的同步問題就消除了。原來使用inode+offset查找page cache變成經過file address space+offset;原來struct page 中的inode成員被address_space類型的mapping成員取代。這個改進還使得匿名內存的共享成爲可能(這個在2.2很難實現,許多討論過)。


4. 虛存系統則從freeBSD借鑑了不少經驗,針對2.2的問題做了巨大的調整。

文檔:RFC: design for new VM不可不讀。

因爲時間倉促,新vm的不少細微之處我也還沒來得及搞清楚。先大體羅列一下,之後我將進一步完善本文,爭取把問題說清楚。另外,等這學期考試事後,我但願能爲你們提供一些詳細註釋過的源代碼。


Lisoleg收集了一些內存管理方面的文檔和連接,你們能夠看一看。

http://www.lisoleg.net/lisoleg/memory/index.html

[目錄]


mlock代碼分析


        系統調用mlock的做用是屏蔽內存中某些用戶進程所要求的頁。
        mlock調用的語法爲:
                int sys_mlock(unsigned long start, size_t len);
初始化爲:
        len=(len+(start &~PAGE_MASK)+ ~PAGE_MASK)&PAGE_MASK;
start &=PAGE_MASK;
其中mlock又調用do_mlock(),語法爲:
int do_mlock(unsigned long start, size_t len,int on);
初始化爲:
        len=(len+~PAGE_MASK)&PAGE_MASK;

    由mlock的參數可看出,mlock對由start所在頁的起始地址開始,長度爲len(注:len=(len+(start&~PAGE_MASK)+ ~PAGE_MASK)&PAGE_MASK)的內存區域的頁進行加鎖。
sys_mlock若是調用成功返回,這其中全部的包含具體內存區域的頁必須是常駐內存的,或者說在調用munlock 或 munlockall以前這部分被鎖住的頁面必須保留在內存。固然,若是調用mlock的進程終止或者調用exec執行其餘程序,則這部分被鎖住的頁面被釋放。經過fork()調用所建立的子進程不可以繼承由父進程調用mlock鎖住的頁面。
    內存屏蔽主要有兩個方面的應用:實時算法和高度機密數據的處理。實時應用要求嚴格的分時,好比調度,調度頁面是程序執行延時的一個主要因素。保密安全軟件常常處理關鍵字節,好比密碼或者密鑰等數據結構。頁面調度的結果是有可能將這些重要字節寫到外存(如硬盤)中去。這樣一些黑客就有可能在這些安全軟件刪除這些在內存中的數據後還能訪問部分在硬盤中的數據。        而對內存進行加鎖徹底能夠解決上述難題。
    內存加鎖不使用壓棧技術,即那些經過調用mlock或者mlockall被鎖住屢次的頁面能夠經過調用一次munlock或者munlockall釋放相應的頁面
    mlock的返回值分析:若調用mlock成功,則返回0;若不成功,則返回-1,而且errno被置位,進程的地址空間保持原來的狀態。返回錯誤代碼分析以下:
   ENOMEM:部分具體地址區域沒有相應的進程地址空間與之對應或者超出了進程所容許的最大可鎖頁面。
   EPERM:調用mlock的進程沒有正確的優先權。只有root進程才容許鎖住要求的頁面。
EINVAL:輸入參數len不是個合法的正數。


mlock所用到的主要數據結構和重要常量

1.mm_struct
struct mm_struct {
        int count;
        pgd_t * pgd; /* 進程頁目錄的起始地址,如圖2-3所示 */
        unsigned long context;
        unsigned long start_code, end_code, start_data, end_data;
        unsigned long start_brk, brk, start_stack, start_mmap;
        unsigned long arg_start, arg_end, env_start, env_end;
        unsigned long rss, total_vm, locked_vm;
        unsigned long def_flags;
        struct vm_area_struct * mmap;     /* 指向vma雙向鏈表的指針 */
        struct vm_area_struct * mmap_avl; /* 指向vma AVL樹的指針 */
        struct semaphore mmap_sem;
}
start_code、end_code:進程代碼段的起始地址和結束地址。
start_data、end_data:進程數據段的起始地址和結束地址。
arg_start、arg_end:調用參數區的起始地址和結束地址。
env_start、env_end:進程環境區的起始地址和結束地址。
rss:進程內容駐留在物理內存的頁面總數。


2. 虛存段(vma)數據結構:vm_area_atruct

虛存段vma由數據結構vm_area_atruct(include/linux/mm.h)描述:
struct vm_area_struct {
        struct mm_struct * vm_mm;        /* VM area parameters */
        unsigned long vm_start;
        unsigned long vm_end;
        pgprot_t vm_page_prot;
        unsigned short vm_flags;
/* AVL tree of VM areas per task, sorted by address */
        short vm_avl_height;
        struct vm_area_struct * vm_avl_left;
        struct vm_area_struct * vm_avl_right;
/* linked list of VM areas per task, sorted by address */
        struct vm_area_struct * vm_next;
/* for areas with inode, the circular list inode->i_mmap */
/* for shm areas, the circular list of attaches */
/* otherwise unused */
        struct vm_area_struct * vm_next_share;
        struct vm_area_struct * vm_prev_share;
/* more */
        struct vm_operations_struct * vm_ops;
        unsigned long vm_offset;
        struct inode * vm_inode;
        unsigned long vm_pte;                        /* shared mem */
};

vm_start;//所對應內存區域的開始地址
vm_end; //所對應內存區域的結束地址
vm_flags; //進程對所對應內存區域的訪問權限
vm_avl_height;//avl樹的高度
vm_avl_left; //avl樹的左兒子
vm_avl_right; //avl樹的右兒子
vm_next;// 進程所使用的按地址排序的vm_area鏈表指針
vm_ops;//一組對內存的操做
    這些對內存的操做是當對虛存進行操做的時候Linux系統必須使用的一組方法。好比說,當進程準備訪問某一虛存區域可是發現此區域在物理內存不存在時(缺頁中斷),就激發某種對內存的操做執行正確的行爲。這種操做是空頁(nopage)操做。當Linux系統按需調度可執行的頁面映象進入內存時就使用這種空頁(nopage)操做。
    當一個可執行的頁面映象映射到進程的虛存地址時,一組vm_area_struct結構的數據結構(vma)就會生成。每個vm_area_struct的數據結構(vma)表明可執行的頁面映象的一部分:可執行代碼,初始化數據(變量),非初始化數據等等。Linux系統能夠支持大量的標準虛存操做,當vm_area_struct數據結構(vma)一被建立,它就對應於一組正確的虛存操做。
    屬於同一進程的vma段經過vm_next指針鏈接,組成鏈表。如圖2-3所示,struct mm_struct結構的成員struct vm_area_struct * mmap  表示進程的vma鏈表的表頭。
    爲了提升對vma段 查詢、插入、刪除操做的速度,LINUX同時維護了一個AVL(Adelson-Velskii and Landis)樹。在樹中,全部的vm_area_struct虛存段均有左指針vm_avl_left指向相鄰的低地址虛存段,右指針vm_avl_right指向相鄰的高地址虛存段,如圖2-5。struct mm_struct結構的成員struct vm_area_struct * mmap_avl表示進程的AVL樹的根,vm_avl_height表示AVL樹的高度。
    對平衡樹mmap_avl的任何操做必須知足平衡樹的一些規則:
Consistency and balancing rulesJ(一致性和平衡規則):

tree->vm_avl_height==1+max(heightof(tree->vm_avl_left),heightof(
tree->vm_avl_right))
abs( heightof(tree->vm_avl_left) - heightof(tree->vm_avl_right) ) <= 1
foreach node in tree->vm_avl_left: node->vm_avl_key <= tree->vm_avl_key,        foreach node in tree->vm_avl_right: node->vm_avl_key >= tree->vm_avl_key.
        注:其中node->vm_avl_key= node->vm_end

對vma能夠進行加鎖、加保護、共享和動態擴展等操做。

3.重要常量
    mlock系統調用所用到的重要常量有:PAGE_MASK、PAGE_SIZE、PAGE_SHIFT、RLIMIT_MEMLOCK、VM_LOCKED、 PF_SUPERPRIV等。它們的值分別以下:
        PAGE_SHIFT                        12                                // PAGE_SHIFT determines the page size
        PAGE_SIZE                        0x1000                        //1UL<<PAGE_SHIFT
        PAGE_MASK                        ~(PAGE_SIZE-1)        //a very useful constant variable
        RLIMIT_MEMLOCK                8                                //max locked-in-memory address space
        VM_LOCKED                        0x2000                        //8*1024=8192, vm_flags的標誌之一。
        PF_SUPERPRIV                0x00000100                //512,

 

mlock系統調用代碼函數功能分析

下面對各個函數的功能做詳細的分析((1)和(2)在前面簡介mlock時已介紹過,並在後面有詳細的程序流程):
suser():若是用戶有效(即current->euid == 0        ),則設置進程標誌爲root優先權(current->flags |= PF_SUPERPRIV),並返回1;不然返回0。
find_vma(struct mm_struct * mm, unsigned long addr):輸入參數爲當前進程的mm、須要加鎖的開始內存地址addr。find_vma的功能是在mm的mmap_avl樹中尋找第一個知足mm->mmap_avl->vm_start<=addr< mm->mmap_avl->vm_end的vma,若是成功則返回此vma;不然返回空null。
mlock_fixup(struct vm_area_struct * vma, unsigned long start, unsigned long end, unsigned int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要加鎖內存區域起始地址和結束地址、須要修改的標誌(0:加鎖,1:釋放鎖)。
merge_segments(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr):輸入參數爲當前進程的mm、須要加鎖的開始內存地址start_addr和結束地址end_addr。merge_segments的功能的是盡最大可能歸併相鄰(即內存地址偏移量連續)並有相同屬性(包括vm_inode,vm_pte,vm_ops,vm_flags)的內存段,在這過程當中冗餘的vm_area_structs被釋放,這就要求vm_mmap鏈按地址大小排序(咱們不須要遍歷整個表,而只須要遍歷那些交叉或者相隔必定連續區域的鄰接vm_area_structs)。固然在缺省的狀況下,merge_segments是對vm_mmap_avl樹進行循環處理,有多少能夠合併的段就合併多少。
mlock_fixup_all(struct vm_area_struct * vma, int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要修改的標誌(0:加鎖,1:釋放鎖)。mlock_fixup_all的功能是根據輸入參數newflags修改此vma的vm_flags。
mlock_fixup_start(struct vm_area_struct * vma,unsigned long end, int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要加鎖內存區域結束地址、須要修改的標誌(0:加鎖,1:釋放鎖)。mlock_fixup_start的功能是根據輸入參數end,在內存中分配一個新的new_vma,把原來的vma分紅兩個部分: new_vma和vma,其中new_vma的vm_flags被設置成輸入參數newflags;而且按地址(new_vma->start和new_vma->end)大小序列把新生成的new->vma插入到當前進程mm的mmap鏈或mmap_avl樹中(缺省狀況下是插入到mmap_avl樹中)。
        注:vma->vm_offset+= vma->vm_start-new_vma->vm_start;
mlock_fixup_end(struct vm_area_struct * vma,unsigned long start, int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要加鎖內存區域起始地址、須要修改的標誌(0:加鎖,1:釋放鎖)。mlock_fixup_end的功能是根據輸入參數start,在內存中分配一個新的new_vma,把原來的vma分紅兩個部分:vma和new_vma,其中new_vma的vm_flags被設置成輸入參數newflags;而且按地址大小序列把new->vma插入到當前進程mm的mmap鏈或mmap_avl樹中。
        注:new_vma->vm_offset= vma->vm_offset+(new_vma->vm_start-vma->vm_start);
mlock_fixup_middle(struct vm_area_struct * vma,unsigned long start, unsigned long end, int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要加鎖內存區域起始地址和結束地址、須要修改的標誌(0:加鎖,1:釋放鎖)。mlock_fixup_middle的功能是根據輸入參數start、end,在內存中分配兩個新vma,把原來的vma分紅三個部分:left_vma、vma和right_vma,其中vma的vm_flags被設置成輸入參數newflags;而且按地址大小序列把left->vma和right->vma插入到當前進程mm的mmap鏈或mmap_avl樹中。
        注:vma->vm_offset += vma->vm_start-left_vma->vm_start;
                right_vma->vm_offset += right_vma->vm_start-left_vma->vm_start;
kmalloc():將在後面3.3中有詳細討論。
insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vmp):輸入參數爲當前進程的mm、須要插入的vmp。insert_vm_struct的功能是按地址大小序列把vmp插入到當前進程mm的mmap鏈或mmap_avl樹中,而且把vmp插入到vmp->inode的i_mmap環(循環共享鏈)中。
avl_insert_neighbours(struct vm_area_struct * new_node,** ptree,** to_the_left,** to_the_right):輸入參數爲當前須要插入的新vma結點new_node、目標mmap_avl樹ptree、新結點插入ptree後它左邊的結點以及它右邊的結點(左右邊結點按mmap_avl中各vma->vma_end大小排序)。avl_insert_neighbours的功能是插入新vma結點new_node到目標mmap_avl樹ptree中,而且調用avl_rebalance以保持ptree的平衡樹特性,最後返回new_node左邊的結點以及它右邊的結點。
avl_rebalance(struct vm_area_struct *** nodeplaces_ptr, int count):輸入參數爲指向vm_area_struct指針結構的指針數據nodeplaces_ptr[](每一個元素表示須要平衡的mmap_avl子樹)、數據元素個數count。avl_rebalance的功能是從nodeplaces_ptr[--count]開始直到nodeplaces_ptr[0]循環平衡各個mmap_avl子樹,最終使整個mmap_avl樹平衡。
down(struct semaphore * sem):輸入參數爲同步(進入臨界區)信號量sem。down的功能根據當前信號量的設置狀況加鎖(阻止別的進程進入臨界區)並繼續執行或進入等待狀態(等待別的進程執行完成退出臨界區並釋放鎖)。
        down定義在/include/linux/sched.h中:
extern inline void down(struct semaphore * sem)
{
        if (sem->count <= 0)
                __down(sem);
        sem->count--;
}
up(struct semaphore * sem)輸入參數爲同步(進入臨界區)信號量sem。up的功能根據當前信號量的設置狀況(當信號量的值爲負數:表示有某個進程在等待使用此臨界區 )釋放鎖。
        up定義在/include/linux/sched.h中:
extern inline void up(struct semaphore * sem)
{
        sem->count++;
        wake_up(&sem->wait);
        }
kfree_s(a,b):kfree_s定義在/include/linux/malloc.h中:#define kfree_s(a,b) kfree(a)。而kfree()將在後面3.3中詳細討論。
avl_neighbours(struct vm_area_struct * node,* tree,** to_the_left,** to_the_right):輸入參數爲做爲查找條件的vma結點node、目標mmap_avl樹tree、node左邊的結點以及它右邊的結點(左右邊結點按mmap_avl中各vma->vma_end大小排序)。avl_ neighbours的功能是根據查找條件node在目標mmap_avl樹ptree中找到node左邊的結點以及它右邊的結點,並返回。
avl_remove(struct vm_area_struct * node_to_delete, ** ptree):輸入參數爲須要刪除的結點node_to_delete和目標mmap_avl樹ptree。avl_remove的功能是在目標mmap_avl樹ptree中找到結點node_to_delete並把它從平衡樹中刪除,而且調用avl_rebalance以保持ptree的平衡樹特性。
remove_shared_vm_struct(struct vm_area_struct *mpnt):輸入參數爲須要從inode->immap環中刪除的vma結點mpnt。remove_shared_vm_struct的功能是從擁有vma結點mpnt 的inode->immap環中刪除的該結點。

 

[目錄]


memory.c


Memory.c中,Linux提供了對虛擬內存操做的若干函數,其中包括對虛擬頁的複製、新建頁表、清除頁表、處理缺頁中斷等等。

1.static inline void copy_page(unsigned long from, unsigned long to)
爲了節約內存的使用,在系統中,各進程一般採用共享內存,即不一樣的進程能夠共享同一段代碼段或數據段。當某一進程發生對共享的內存發生寫操做時,爲了避免影響其它進程的正常運行,系統將把該內存塊複製一份,供須要寫操做的進程使用,這就是所謂的copy-on-write機制。copy_page就是提供複製內存功能的函數,它調用C語言中標準的內存操做函數,將首地址爲from的一塊虛擬內存頁複製到首地址爲to的空間中。

二、void clear_page_tables(struct task_struct * tsk)
clear_page_table的功能是將傳入的結構tsk中的pgd頁表中的全部項都清零,同時將二級頁表所佔的空間都釋放掉。傳入clear_page_tables的是當前進程的tsk結構,取得該進程的一級頁目錄指針pgd後,採用循環的方式,調用free_one_pgd清除pgd表。表共1024項。在free_one_pgd中,實際執行的功能只調用一次free_one_pmd(在80x86中,因爲硬件的限制,只有兩級地址映射,故將pmd與pgd合併在一塊兒)。在free_one_pmd中,函數調用pte_free將對應於pmd的二級頁表所佔的物理空間釋放掉(進程代碼、數據所用的物理內存在do_munmap釋放掉了)並將pmd賦值爲零。
clear_page_table在系統啓動一個可執行文件的映象或載入一個動態連接庫時被調用。在fs/exec.c中的do_load_elf_binary()或do_load_aout_binary()調用flash_old_exec,後者調用exec_mmap,而exec_mmap調用clear_page_table。其主要功能是當啓動一個新的應用程序的時候,將複製的mm_struct中的頁表清除乾淨,並釋放掉原有的全部二級頁表空間。

三、void oom(struct task_struct * task)
返回出錯信息。

四、void free_page_tables(struct mm_struct * mm)
在free_page_table中,大部分的代碼與clear_page_table中的函數一致。所不一樣的是,該函數在最後調用了pgd_free(page_dir),即不光釋放掉二級頁表所佔的空間,同時還釋放一級頁目錄所佔的空間。這是由於free_page_tables被__exit_mm調用,__exit_mm又被do_exit (kernel/kernel.c)調用。當進程停止、系統退出或系統重起時都須要用do_exit(屬於進程管理)將全部的進程結束掉。在結束進程過程當中 ,將調用free_page_table將進程的空間所有釋放掉,固然包括釋放進程一級頁目錄所佔的空間。

五、int new_page_tables(struct task_struct * tsk)
該函數的主要功能是創建新的頁目錄表,它的主要流程如以下:
調用pgd_alloc()爲新的頁目錄表申請一片4K空間 。
將初始化進程的內存結構中從768項開始到1023項的內容複製給新的頁表(全部的進程都共用虛擬空間中 3G~4G的內存,即在覈心態時能夠訪問全部相同的存儲空間)。
調用宏SET_PAGE_DIR(include/asm/pgtable.h)將進程控制塊tsk->ts->CR3的值改成新的頁目錄表的首地址,同時將CPU中的CR3寄存器的值改成新的頁目錄表的首地址,從而使新進程進入本身的運行空間。
將tsk->mm->pgd改成新的頁目錄表的首地址。
new_page_tables被copy_mm調用,而copy_mm被copy_mm_do_fork調用,這兩個函數都在kernel/fork.c中。同時,new_page_tables也能夠在exec_mmap(fs/exec.c)中調用。即新的進程的產生能夠經過兩種途徑,一種是fork,在程序中動態地生成新的進程,這樣新進程的頁表原始信息利用copy_mm從其父進程中繼承而得,另外一種是運行一個可執行文件映象,經過文件系統中的exec.c,將映象複製到tsk結構中。兩種方法都須要調用new_page_tables爲新進程分配頁目錄表。

六、static inline void copy_one_pte(pte_t * old_pte, pte_t * new_pte, int cow)
將原pte頁表項複製到new_pte上,其流程以下:
檢測old_pte是否在內存中,如不在物理內存中,調用swap_duplicate按old_pte在swap file中的入口地址,將old_pte複製到內存中,同時把old_pte的入口地址賦給new_pte並返回。反之轉向3。
獲取old_pte對應的物理地址的頁號。
根據頁號判斷old_pte是否爲系統保留的,若是爲系統保留的,這些頁爲全部的進程在覈心態下使用,用戶進程沒有寫的權利,則只需將old_pte指針直接轉賦給new_pte後返回。反之則該pte屬於普通內存的,則轉向4。
根據傳入的C-O-W標誌,爲old_pte置寫保護標誌,若是該頁是從swap_cache中得來的,將old_pte頁置上「dirty」標誌。將old_pte賦值給new_pte。
將mem_map結構中關於物理內存使用進程的個數的數值count加1。

七、static inline int copy_pte_range(pmd_t *dst_pmd, pmd_t *src_pmd,
unsigned long address, unsigned long size, int cow)
經過循環調用copy_one_pte將從源src_pmd中以地址address開始的長度爲size的空間複製給dst_pmd中。如dst_pmd中還未分配地址爲address的頁表項,則先給三級頁表pte表分配4K空間。(每調用一次copy_one_pte複製4K空間。在一次copy_pte_range中最多可複製4M空間)。

八、static inline int copy_pmd_range(pgd_t *dst_pgd, pgd_t *src_pgd,
unsigned long address, unsigned long size, int cow)
經過循環調用copy_pte_range將從源src_pgd中以地址address開始的長度爲size的空間複製給dst_pgd中。如dst_pgd中還未分配地址爲address的頁表項,則在一級(同時也是二級)頁表中給對應的pmd分配目錄項。

九、int copy_page_range(struct mm_struct *dst, struct mm_struct *src,
                        struct vm_area_struct *vma)
該函數的主要功能是將某個任務或進程的vma塊複製給另外一個任務或進程。其工做機制是循環調用copy_pmd_range,將vma塊中的全部虛擬空間複製到對應的虛擬空間中。在作複製以前,必須確保新任務對應的被複制的虛擬空間中必須都爲零。copy_page_range按dup_mmap()->copy_mm()->do_fork()的順序被調用(以上三個函數均在kernel/fork.c中)。當進程被建立的時候,須要從父進程處複製全部的虛擬空間,copy_page_range完成的就是這個任務。copy_page_range的函數調用關係見圖8。

九、static inline void free_pte(pte_t page)
虛存頁page如在內存中,且不爲系統的保留內存,調用free_page將其釋放掉(如在系統保留區中,則爲全系統共享,故不能刪除)。
如page在swap file中,調用swap_free()將其釋放。

十、static inline void forget_pte(pte_t page)
如page不爲空,調用free_pte將其釋放。

十一、static inline void zap_pte_range(pmd_t * pmd, unsigned long address,
unsigned long size)
zap爲zero all pages的縮寫。該函數的做用是將在pmd中從虛擬地址address開始,長度爲size的內存塊經過循環調用pte_clear將其頁表項清零,調用free_pte將所含空間中的物理內存或交換空間中的虛存頁釋放掉。在釋放以前,必須檢查從address開始長度爲size的內存塊有無越過PMD_SIZE.(溢出則可以使指針逃出0~1023的區間)。

十二、static inline void zap_pmd_range(pgd_t * dir, unsigned long address, unsigned long size)
函數結構與zap_pte_range相似,經過調用zap_pte_range完成對全部落在address到address+size區間中的全部pte的清零工做。zap_pmd_range至多清除4M空間的物理內存。

1三、int zap_page_range(struct mm_struct *mm, unsigned long address, unsigned long size)
函數結構與前兩個函數相似。將任務從address開始到address+size長度內的全部對應的pmd都清零。zap_page_range的主要功能是在進行內存收縮、釋放內存、退出虛存映射或移動頁表的過程當中,將不在使用的物理內存從進程的三級頁表中清除。(在討論clear_page_tables時,就提到過當進程退出時,釋放頁表以前,先保證將頁表對應項清零,保證在處於退出狀態時,進程不佔用0~3G的空間。)


1四、static inline void zeromap_pte_range(pte_t * pte, unsigned long address,
unsigned long size, pte_t zero_pte)
1五、static inline int zeromap_pmd_range(pmd_t * pmd, unsigned long address,
unsigned long size, pte_t zero_pte)
1六、int zeromap_page_range(unsigned long address, unsigned long size, pgprot_t prot)
這三個函數與前面的三個函數從結構上看很類似,他們的功能是將虛擬空間中從地址address開始,長度爲size的內存塊所對應的物理內存都釋放掉,同時將指向這些區域的pte都指向系統中專門開出的長度爲4K,全爲0的物理頁。zeromap_page_range在kernel代碼中沒有被引用,這個函數是舊版本的Linux遺留下來的,在新版本中已經被zap_page_range所替代。

1七、static inline void remap_pte_range(pte_t * pte, unsigned long address,
unsigned long size,        unsigned long offset, pgprot_t prot)
1八、static inline int remap_pmd_range(pmd_t * pmd, unsigned long address,
unsigned long size,        unsigned long offset, pgprot_t prot)
1九、int remap_page_range(unsigned long from, unsigned long offset, unsigned long size,
pgprot_t prot)
這三個函數也同前面的函數同樣,層層調用,現僅介紹一下最後一個函數的做用。remap_page_range的功能是將原先被映射到虛擬內存地址from處的,大小爲size的虛擬內存塊映射到以偏移量offset爲起始地址的虛擬內存中,同時將原來的pte、pmd項都清零。該函數也是逐級調用,在remap_pte_range中,經過set_pte將的物理頁映射到新的虛擬內存頁表項pte上。remap_page_range函數的功能與下文中的remap.c中介紹的功能相近,所以在kernel中也沒有用到。

20、unsigned long put_dirty_page(struct task_struct * tsk, unsigned long page,
unsigned long address)
將虛擬內存頁page連接到任務tsk中虛擬地址爲address的虛擬內存中,其主要調用的流程以下:put_dirty_page->setup_arg_page->do_load_xxx_binary(xxx爲aout或elf,這些函數都在fs/exec.c中),它的功能是將在載入可執行文件的時候,將其相關的堆棧信息、環境變量等複製到當前進程的空間上。

2一、void handle_mm_fault(struct vm_area_struct * vma, unsigned long address,
int write_access)
        用於處理ALPHA機中的缺頁中斷

 

[目錄]


mmap.c


        mmap.c
在mmap.c中,主要提供了對進程內存管理進行支持的函數,主要包括了do_mmap、do_munmap等對進程的虛擬塊堆avl數進行管理的函數。

有關avl樹的一些操做:
一、static inline void avl_neighbours (struct vm_area_struct * node, struct vm_area_struct * tree, struct vm_area_struct ** to_the_left, struct vm_area_struct ** to_the_right)
尋找avl樹tree中的節點node的前序節點和後序節點,將結果放在指針to_the_left和to_the_right中,即便得*to_the_left->next=node,node->next=*to_the_right。在實際搜索中,過程是找到node節點中的左節點的最右節點和右節點的最左節點,採用avl樹搜索能夠提升效率。

二、static inline void avl_rebalance (struct vm_area_struct *** nodeplaces_ptr, int count)
將因爲插入操做或刪除操做而形成不平衡的avl樹恢復成平衡狀態。nodeplaces_ptr是指向的是須要調整的子樹的根節點,count是該子樹的高度。

static inline void avl_insert (struct vm_area_struct * new_node,
struct vm_area_struct ** ptree)
將新節點new_node插入avl樹ptree中,並將該樹從新生成平衡avl樹。在建立avl樹時,將vma模塊不斷的插入avl樹中,構建一個大的avl樹。當進程建立時,複製父進程後須要將以雙向鏈表拷貝過來的vma鏈生成avl樹。

四、static inline void avl_insert_neighbours (struct vm_area_struct * new_node, struct vm_area_struct ** ptree,        struct vm_area_struct ** to_the_left, struct vm_area_struct ** to_the_right)
將新節點new_node插入avl樹ptree中,並將該樹從新生成平衡avl樹,同時返回該新節點的前序節點和後序節點。

五、static inline void avl_remove (struct vm_area_struct * node_to_delete, struct vm_area_struct ** ptree)
將指定要刪除的節點node_to_delete從avl樹ptree中刪除。並將該樹從新生成平衡avl樹。該函數在釋放虛存空間和歸併vma鏈表是使用。

七、static void printk_list (struct vm_area_struct * vma)
八、static void printk_avl (struct vm_area_struct * tree)
九、static void avl_checkheights (struct vm_area_struct * tree)
十、static void avl_checkleft (struct vm_area_struct * tree, vm_avl_key_t key)
十一、static void avl_checkright (struct vm_area_struct * tree, vm_avl_key_t key)
十二、static void avl_checkorder (struct vm_area_struct * tree)
1三、static void avl_check (struct task_struct * task, char *caller)
這些函數都是系統調試時用以檢測avl樹結構的正確性

1四、static inline int vm_enough_memory(long pages)
經過計算當前系統中所剩的空間判斷是否足夠調用。可以使用的內存包括緩衝存儲器、頁緩存、主存中的空閒頁、swap緩存等。

1五、static inline unsigned long vm_flags(unsigned long prot, unsigned long flags)
提供宏功能將頁的保護位和標誌位合併起來。

1六、unsigned long get_unmapped_area(unsigned long addr, unsigned long len)
從虛擬內存address開始找到未分配的連續空間大於len的虛擬空間塊,並將該快的首地址返回。

1七、unsigned long do_mmap(struct file * file, unsigned long addr, unsigned long len,
        unsigned long prot, unsigned long flags, unsigned long off)
do_mmap在Linux虛擬內存管理中是一個很重要的函數,它的主要功能是將可執行文件的映象映射到虛擬內存中,或將別的進程中的內存信息映射到該進程的虛擬空間中。並將映射了的虛擬塊的vma加入到該進程的vma avl樹中。其運行的流程以下,更詳細的分析請參閱林濤同窗和徐玫峯同窗的報告。
檢驗給定的映射長度len是大於1頁,小於一個任務的最大長度3G且加上進程的加上偏移量off不會溢出。如不知足則退出。
若是當前任務的內存是上鎖的,檢驗加上len後是否會超過當前進程上鎖長度的界限。如是則退出。
若是從文件映射,檢驗文件是否有讀的權限。如無在退出。
調用get_unmaped取得從地址address開始未映射的連續虛擬空間大於len的虛存塊。
如從文件映射,保證該文件控制塊有相應的映射操做。
爲映射組織該區域申請vma結構。
調用vm_enough_memory有足夠的內存。如無則釋放6中申請的vma,退出。
若是是文件映射,調用file->f_op_mmap將該文件映射如vma中。
調用insert_vm_struct將vma插入該進程的avl樹中。
歸併該avl樹。

1八、void merge_segments (struct mm_struct * mm, unsigned long start_addr,
unsigned long end_addr)
通過對進程虛擬空間不斷的映射,在進程中的vma塊有許可能是能夠合併的,爲了提升avl樹查找的效率,減小avl樹中沒必要要的vma塊,一般須要將這些塊和並,merge_segments的功能爲合併虛擬空間中從start_addr到end_addr中類型相同,首尾相連的vma塊。因爲只有通過增長操做採有可能合併,全部merge_segments只在do_mmap和unmap_fixup中被調用。該函數的流程以下:
根據起始地址start_addr從找到第一塊知足vm_end>start_addr的vma塊mpnt。
調用avl_neighbours找到在vma雙向鏈表上與mpnt先後相連的vma塊prev和next。
若是prev和mpnt首尾相連,且有一樣在swap file中的節點,一樣的標誌,一樣的操做等則將其合併,反之轉向6。
調用avl_remove將mpnt從avl樹中刪除,調整prev的結束地址和後序指針。
將結構mpnt所佔的物理空間刪除。
prev、mpnt、next依次下移,如未超過end_addr則返回3。

1九、static void unmap_fixup(struct vm_area_struct *area, unsigned long addr, size_t len)
釋放虛擬空間中的某些區域的時候,將會出現四種狀況:
將整個vma釋放掉
將vma的前半部分釋放掉
將vma的後半部分釋放掉
將vma的中間部分釋放掉
爲了正常維護vma樹,當第一種狀況是,將整個vma釋放掉。同時釋放vma結構所佔的空間。第二種,釋放後半部分,修改vma的相關信息。第二種,釋放前半部分,修改vma的相關信息。第四種,因爲在vma中出現了一個洞,則需增長一個vma結構描述新出現的vma塊。unmap_fixup所執行的工做就是當釋放空間時,修正對vma樹的影響。

20、int do_munmap(unsigned long addr, size_t len)
do_munmap將釋放落在從地址addr開始,長度爲len空間內的vma所對應的虛擬空間。do_munmap被系統調用sys_munmap所調用(對sys_munmap如何工做的不甚瞭解)。下面是該函數的流程:
經過find_vma根據addr找到第一塊vma->end>addr的vma塊mpnt。
調用avl_neighbours找到mpnt在鏈表中的相鄰指針prev和next。
將檢查中全部與虛擬空間addr~addr+len相交的vma塊放入free鏈表中。同時若是該vma連接在共享內存中,則將其從該環形鏈表中釋放出來。
按序搜索free鏈表,調用unmap_fixup釋放空間。
調用zap_page_range將指向釋放掉的虛擬空間中的pte頁表項清零。
調用kfree釋放mpnt結構佔用的空間。

remap.c
該文件提供了對虛擬內存重映射的若干函數。在下文中將介紹這些函數的功能,分析這些函數在虛擬內存管理中所起的做用。同時詳細介紹其中主要函數的流程。

static inline pte_t *get_one_pte(struct mm_struct *mm, unsigned long addr)
根據輸入的虛存地址返回其在虛擬內存中的對應的頁表項pte。

static inline pte_t *alloc_one_pte(struct mm_struct *mm, unsigned long addr)
根據輸入的虛存地址addr在pgd表中根據三級頁表映射機制找pte,若是在pgd表中無對應的項,則分配給一個pgd(pmd)表項,在這個表項內分配根據addr分配pte,將pte返回。

static inline int copy_one_pte(pte_t * src, pte_t * dst)
將目的pte(dst)表項中的值賦成源pte(src)中的值,而後將源pte中的值清零,根據這函數的功能取move_one_pte更合適。

static int move_one_page(struct mm_struct *mm,
         unsigned long old_addr, unsigned long new_addr)
根據輸入的虛擬地址old_addr調用get_one_pte獲取該地址在三級頁表中的pte項,調用copy_one_pte將該pte對應的物理頁指針移到根據new_addr對應的pte項上,即在虛擬空間內移動一虛擬內存頁。

static int move_page_tables(struct mm_struct * mm,
         unsigned long new_addr, unsigned long old_addr, unsigned long len)
將虛擬地址空間中從old_addr開始的長度爲len的虛擬內存移動到以new_addr爲起始地點的的虛擬空間中,如下爲該函數的流程:
將所需移動的內存長度len賦值給偏移量offset若是offset=0,結束。反之轉向2。
將偏移量offset減去一個頁的長度,調用move_one_page將從old_addr+offset開始的一頁移到new_addr+offset。若發生錯誤則轉到4。
若是offset不爲0,則轉向1,反之結束。
調用move_one_page將全部已移動到新地址的頁移回源地址,調用zap_page_range將從new_addr開始的移動過的頁pte清零,並返回出錯信息-1。

static inline unsigned long move_vma(struct vm_area_struct * vma,
        unsigned long addr, unsigned long old_len, unsigned long new_len)
將虛存中的vma塊vma的起始地址爲addr,長度爲old_len的內存塊擴展爲長度爲new_len的內存塊,並在虛存中找到可容納長度爲new_len塊的連續區域,返回首地址。其工做流程以下:
給新的vma結構塊new_vma分配空間,若是不成功返回出錯信息。
調用get_unmap_area從addr開始找到第一個未被利用的虛存空洞,空洞長度大於給定的新的虛擬內存塊的長度len,將其首地址賦給new_addr。若是未招到,則轉向9。
調用move_page_tables將從addr開始的長度爲old_len的內存區域移動到以new_addr爲起始地址的虛擬空間中。
修改new_vma塊中關於起始地址,結束地址的值。
將新的new_vma塊插入到當前進程的虛擬內存所鏈成的雙向鏈表和avl樹中。
調用merge_segment將虛擬空間中地址可能連結在一塊兒的不一樣的vma段連結成一個vma塊,同時刪除冗於的vma塊。
將原有空間中的從addr開始,長度爲old_len的虛擬空間釋放掉。
修改mm結構中的全部虛存的長度,返回新的起始虛擬地址new_addr。
將vma塊new_vma釋放掉並返回出錯信息。

asmlinkage unsigned long sys_mremap(unsigned long addr,         unsigned long old_len,
                    unsigned long new_len        unsigned long flags)
sys_remap是一個系統調用,其主要功能是擴展或收縮現有的虛擬空間。它的主要工做流程以下:
檢查addr地址是否小於4096,如小於,則非法,返回。
將原始長度old_len和須要擴展或收縮的長度new_len頁對齊。
若是有old_len>new_len,則說明是收縮空間,調用do_munmap將虛存空間中從new_len到old_len的空間釋放掉。返回收縮後的首地址addr。
根據addr找到第一塊vma塊知足vma->end > addr,檢查addr是否落在虛存的空洞中,如是,則返回出錯信息。
檢查須要擴展的內存塊是否落在該vma塊中,越界則返回出錯信息。
若是該vma是上鎖的,則檢測上鎖的內存擴展後是否越界,如是,則7返回出錯信息 。
檢測當前進程的虛存空間經擴展後是否超過系統給該進程的最大空間。如是,則返回出錯信息。
若是找到vma塊從addr開始到塊末尾的長度爲old_len且(old_len的長度不等於new_len或該虛存是不可移動的),則轉向9,反之轉向10。
檢測從跟隨找到的vma塊的未分配的空間是否大於須要擴展空間。若是大於,則直接將擴展的空間掛在找到的vma塊後,修改vma塊中相關的信息,並返回擴展後虛擬塊的首地址。如小於轉向10。
若是當前虛擬塊是是不可移動的,則返回錯誤信息。反之,調用move_vma將須要擴展的虛擬塊移動能夠容納其長度new_len的虛擬空間中。


對於sys_remap這個系統調用,在何種狀況下激活這個系統調用,使人不解。在Linux中全部的系統調用在代碼中僅在engry.s中有該調用的入口,在程序中有其具體實現,可是使人很難找到這二者在系統運行中是如何有機的結合在一塊兒的。

[目錄]


夥伴(buddy)算法


2.4版內核的頁分配器引入了"頁區"(zone)結構, 一個頁區就是一大塊連續的物理頁面. Linux 2.4將整個物理內存劃分爲3個頁區, DMA頁區(ZONE_DMA), 普通頁區(ZONE_NORMAL)和高端頁區(ZONE_HIGHMEM).
頁區能夠使頁面分配更有目的性, 有利於減小內存碎片. 每一個頁區的頁分配仍使用夥伴(buddy)算法.
夥伴算法將整個頁區劃分爲以2爲冪次的各級頁塊的集合, 相鄰的同次頁塊稱爲夥伴, 一對夥伴能夠合併
到更高次頁面集合中去.

下面分析一下夥伴算法的頁面釋放過程.

; mm/page_alloc.c:

#define BAD_RANGE(zone,x) (((zone) != (x)->zone) || (((x)-mem_map) offset) || (((x)-mem_map) >= (zone)->offset+(zone)->size))

#define virt_to_page(kaddr)        (mem_map + (__pa(kaddr) >> PAGE_SHIFT))
#define put_page_testzero(p)         atomic_dec_and_test(

void free_pages(unsigned long addr, unsigned long order)
{        order是頁塊尺寸指數, 即頁塊的尺寸有(2^order)頁.
        if (addr != 0)
                __free_pages(virt_to_page(addr), order);
}
void __free_pages(struct page *page, unsigned long order)
{
        if (!PageReserved(page)  put_page_testzero(page))
                __free_pages_ok(page, order);
}
static void FASTCALL(__free_pages_ok (struct page *page, unsigned long order));
static void __free_pages_ok (struct page *page, unsigned long order)
{
        unsigned long index, page_idx, mask, flags;
        free_area_t *area;
        struct page *base;
        zone_t *zone;

        if (page->buffers)
                BUG();
        if (page->mapping)
                BUG();
        if (!VALID_PAGE(page))
                BUG();
        if (PageSwapCache(page))
                BUG();
        if (PageLocked(page))
                BUG();
        if (PageDecrAfter(page))
                BUG();
        if (PageActive(page))
                BUG();
        if (PageInactiveDirty(page))
                BUG();
        if (PageInactiveClean(page))
                BUG();

        page->flags  ~((1        page->age = PAGE_AGE_START;

        zone = page->zone; 取page所在的頁區

        mask = (~0UL)         base = mem_map + zone->offset; 求頁區的起始頁
        page_idx = page - base; 求page在頁區內的起始頁號
        if (page_idx  ~mask) 頁號必須在頁塊尺寸邊界上對齊
                BUG();
        index = page_idx >> (1 + order);
                ; 求頁塊在塊位圖中的索引, 每一索引位置表明相鄰兩個"夥伴"
        area = zone->free_area + order; 取該指數頁塊的位圖平面

        spin_lock_irqsave( flags);

        zone->free_pages -= mask; 頁區的自由頁數加上將釋放的頁數(掩碼值爲負)

        while (mask + (1                 struct page *buddy1, *buddy2;

                if (area >= zone->free_area + MAX_ORDER) 若是超過了最高次平面
                        BUG();
                if (!test_and_change_bit(index, area->map)) 測試並取反頁塊的索引位
                        /*
                        * the buddy page is still allocated.
                        */
                        break; 若是原始位爲0, 則說明該頁塊原來沒有夥伴, 操做完成
                /*
                * Move the buddy up one level. 若是原始位爲1, 則說明該頁塊存在一個夥伴
                */
                buddy1 = base + (page_idx ^ -mask); 對頁塊號邊界位取反,獲得夥伴的起點
                buddy2 = base + page_idx;

                if (BAD_RANGE(zone,buddy1)) 夥伴有沒有越過頁區範圍
                        BUG();
                if (BAD_RANGE(zone,buddy2))
                        BUG();

                memlist_del( 刪除夥伴的自由鏈
                mask                 area++; 求更高次位圖平面
                index >>= 1; 求更高次索引號
                page_idx  mask; 求更高次頁塊的起始頁號
        }
        memlist_add_head( + page_idx)->list,  將求得的高次頁塊加入該指數的自由鏈

        spin_unlock_irqrestore( flags);

        /*
        * We don't want to protect this variable from race conditions
        * since it's nothing important, but we do want to make sure
        * it never gets negative.
        */
        if (memory_pressure > NR_CPUS)
                memory_pressure--;
}

 

 

[目錄]


頁目錄處理的宏


對於i386的2級分頁機構,每一個頁目錄字高20位是頁號,低12位是頁屬性.
若是將頁目錄字的低12位屏蔽成0,整個頁目錄字就是相應頁面的物理地址,下面是經常使用的一些頁目錄處理的宏.


typedef struct { unsigned long pgd; } pgd_t;                 一級頁目錄字結構
typedef struct { unsigned long pmd; } pmd_t;                 中級頁目錄字結構
typedef struct { unsigned long pte_low; } pte_t;          末級頁目錄字結構
typedef struct { unsigned long pgprot; } pgprot_t;        頁屬性字結構

pgd_t *pgd = pgd_offset(mm_struct,addr);
        取進程虛擬地址addr的一級頁目錄字指針,擴展爲
        ((mm_struct)->pgd + ((address >> 22)  0x3FF))

pgd_t *pgd = pgd_offset_k(addr)
        取內核地址addr的一級頁目錄字指針,擴展爲
        (init_mm.pgd + ((address >> 22)  0x3FF));

pmd_t *pmd = pmd_offset(pgd, addr) ;
        從一級頁目錄字指針取addr的中級頁錄字指針,在2級分頁系統中,它們的值是相同的,擴展爲
        (pmd_t *)(pgd);

pte_t *pte = pte_offset(pmd, addr)
        從中級頁目錄字指針取addr的末級頁目錄字指針,擴展爲
        (pte_t *)((pmd->pmd  0xFFFFF000) + 0xC0000000) + ((addr >> 12)  0x3FF);

struct page *page = pte_page(pte_val));
        取末級頁目錄字pte_val的頁面映射指針,擴展爲
        (mem_map+(pte_val.pte_low >> 12))

pte_t pte_val = ptep_get_and_clear(pte);
        取末級頁目錄字指針pte的值,並將該目錄字清零,擴展爲
        (pte_t){xchg(

pte_t pte_val = mk_pte(page,pgprot);
        將頁面映射指針page與頁屬性字pgprot組合成頁目錄字,擴展爲
        (pte_t) { (page - mem_map)
pte_t pte_val =  mk_pte_phys(physpage, pgprot);
        將物理地址physpage所在頁面與頁屬性字組合成頁目錄字,擴展爲
        (pte_t) { physpage >> 12
unsigned long addr = pmd_page(pmd_val);
        取中級頁目錄字所表示的頁目錄虛擬地址,擴展爲
        ((unsigned long) (pmd_val.pmd  0xFFFFF000 + 0xC0000000));

set_pte(pte,pte_val);
        設置末級頁目錄字,擴展爲
        *pte = pteval;
set_pmd(pmd,pmd_val)
        設置中級頁目錄字,擴展爲
        *pmd = pmd_val;
set_pgd(pgd,pgd_val)
        設置一級頁目錄字,擴展爲
        *pgd = pgd_val;

 

 

[目錄]


進程

 

[目錄]


信號


struct semaphore {
        atomic_t count; 進程抓取semaphore時減1
        int sleepers; 抓取semaphore失敗時增1
        wait_queue_head_t wait; semaphore的等待隊列
};
        down(&sem) 編繹成:

        movl $sem,% ecx        經過寄存器ecx向__down函數傳遞sem指針
        decl sem
        js 2f 若是爲負值,表示semaphore已被佔用,執行__down_failed過程
1:
因爲出現semaphore競爭的可能性比較小,將分支代碼轉移到.text.lock段,以縮短正常的指令路徑.
.section .text.lock,"ax"
2:        call __down_failed
        jmp 1b
.previous
        ...

        up(&sem) 編繹成:

        movl $sem,% ecx
        incl sem
        jle 2f 若是小於或等於0,表示該semaphore有進程在等待,就去調用__up_wakeup
1:
.section .text.lock,"ax"
2:        call __up_wakeup
        jmp 1b
.previous
        ...
__down_failed:
        pushl % eax
        pushl % edx
        pushl % ecx ; eax,edx,ecx是3個可用於函數參數的寄存器
        call __down
        popl % ecx
        popl % edx
        popl % eax
        ret
__up_wakeup:
        pushl % eax
        pushl % edx
        pushl % ecx
        call __up
        popl % ecx
        popl % edx
        popl % eax
        ret
; semaphore.c
void __down(struct semaphore * sem)
{
        struct task_struct *tsk = current;
        DECLARE_WAITQUEUE(wait, tsk);
        tsk->state = TASK_UNINTERRUPTIBLE;
        add_wait_queue_exclusive(&sem->wait, &wait);
        // 將當前進程加入到該semaphore的等待隊列中

        spin_lock_irq(&semaphore_lock);
        sem->sleepers++;
        for (;;) {
                int sleepers = sem->sleepers;

                /*
                * Add "everybody else" into it. They aren't
                * playing, because we own the spinlock.
                */

                // atomic_add_negative(int i,atomic_t *v)將i + v->counter相加,
                // 結果爲負返回1,不然返回0
                if (!atomic_add_negative(sleepers - 1, &sem->count)) {
                // 若是(sleepers - 1 + sem->count.counter)非負,則說明
                // semaphore已經被釋放,能夠返回
                        sem->sleepers = 0;
                        break;
                }
                sem->sleepers = 1;        /* us - see -1 above */

                spin_unlock_irq(&semaphore_lock);
                // 當semaphore被up()喚醒時,schedule()返回
                schedule();
                // 雖然已線程被up恢復,但爲防止碰巧又有一個線程得到了semaphore,
                // 所以將它們放在循環體中
                tsk->state = TASK_UNINTERRUPTIBLE;
                spin_lock_irq(&semaphore_lock);
        }
        spin_unlock_irq(&semaphore_lock);
        // 該進程得到了semaphore,將它從等待隊列中刪除
        remove_wait_queue(&sem->wait, &wait);
        tsk->state = TASK_RUNNING;
        // 爲何這裏要調用wake_up,是由於調用它沒有反作用從而防止潛在的死鎖嗎?
        wake_up(&sem->wait);
}
void __up(struct semaphore *sem)
{
擴展爲
__wake_up_common(&sem->wait,TASK_UNINTERRUPTIBLE|TASK_INTERRUPTIBLE,1,0);
喚醒隊列中第1個進程,即將第1個進程放入運行隊列
        wake_up(&sem->wait);
}
; sched.c
static inline void __wake_up_common (wait_queue_head_t *q, unsigned int
mode,
                                     int nr_exclusive, const int sync)
{
        struct list_head *tmp, *head;
        struct task_struct *p;
        unsigned long flags;

        if (!q)
                goto out;

        wq_write_lock_irqsave(&q->lock, flags);

#if WAITQUEUE_DEBUG
        CHECK_MAGIC_WQHEAD(q);
#endif

        head = &q->task_list;
#if WAITQUEUE_DEBUG
        if (!head->next || !head->prev)
                WQ_BUG();
#endif
        tmp = head->next;
        while (tmp != head) {
                unsigned int state;
                wait_queue_t *curr = list_entry(tmp, wait_queue_t,
task_list);
                tmp = tmp->next;

#if WAITQUEUE_DEBUG
                CHECK_MAGIC(curr->__magic);
#endif
                p = curr->task;
                state = p->state;
                if (state & mode) {
#if WAITQUEUE_DEBUG
                        curr->__waker = (long)__builtin_return_address(0);
#endif
                        if (sync)
                                wake_up_process_synchronous(p);
                        else
                                wake_up_process(p);
                        if ((curr->flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                                break;
                }
        }
        wq_write_unlock_irqrestore(&q->lock, flags);
out:
        return;
}
; sched.c
inline void wake_up_process(struct task_struct * p)
{
        unsigned long flags;

        /*
        * We want the common case fall through straight, thus the goto.
        */
        spin_lock_irqsave(&runqueue_lock, flags);
        p->state = TASK_RUNNING;
        if (task_on_runqueue(p))
                goto out;
        add_to_runqueue(p);
        reschedule_idle(p);
out:
        spin_unlock_irqrestore(&runqueue_lock, flags);
}
; sched.c
static inline void wake_up_process_synchronous(struct task_struct * p)
{
        unsigned long flags;

        /*
        * We want the common case fall through straight, thus the goto.
        */
        spin_lock_irqsave(&runqueue_lock, flags);
        p->state = TASK_RUNNING;
        if (task_on_runqueue(p))
                goto out;
        add_to_runqueue(p);
out:
        spin_unlock_irqrestore(&runqueue_lock, flags);
}
; sched.h
static inline int task_on_runqueue(struct task_struct *p)
{
        return (p->run_list.next != NULL);
}
; sched.c
static inline void add_to_runqueue(struct task_struct * p)
{
        list_add(&p->run_list, &runqueue_head);
        nr_running++;
}
static LIST_HEAD(runqueue_head);

; fork.c
void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *
wait)
{
        unsigned long flags;

        wq_write_lock_irqsave(&q->lock, flags);
        wait->flags = WQ_FLAG_EXCLUSIVE;
        __add_wait_queue_tail(q, wait);
        wq_write_unlock_irqrestore(&q->lock, flags);
}
; wait.h
static inline void __add_wait_queue_tail(wait_queue_head_t *head,
                                                wait_queue_t *new)
{
#if WAITQUEUE_DEBUG
        if (!head || !new)
                WQ_BUG();
        CHECK_MAGIC_WQHEAD(head);
        CHECK_MAGIC(new->__magic);
        if (!head->task_list.next || !head->task_list.prev)
                WQ_BUG();
#endif
        list_add_tail(&new->task_list, &head->task_list);
}
正執行調度的函數是schedule(void),它選擇一個最合適的進程執行,而且真正進行上下文切換,
使得選中的進程得以執行。而reschedule_idle(struct task_struct *p)的做用是爲進程選擇
一個合適的CPU來執行,若是它選中了某個CPU,則將該CPU上當前運行進程的need_resched標誌
置爲1,而後向它發出一個從新調度的處理機間中斷,使得選中的CPU可以在中斷處理返回時執行
schedule函數,真正調度進程p在CPU上執行。在schedule()和reschedule_idle()中調用了goodness()
函數。goodness()函數用來衡量一個處於可運行狀態的進程值得運行的程度。此外,在schedule()
函數中還調用了schedule_tail()函數;在reschedule_idle()函數中還調用了reschedule_idle_slow()。

[目錄]


sched.c


/*
* 'sched.c' is the main kernel file. It contains scheduling primitives
* (sleep_on, wakeup, schedule etc) as well as a number of simple system
* call functions (type getpid(), which just extracts a field from
* current-task
*/
#include
#include
#include
#include
#include
#include
#include
#define LATCH (1193180/HZ)
extern void mem_use(void);
extern int timer_interrupt(void);
extern int system_call(void);
union task_union {
struct task_struct task;
char stack[PAGE_SIZE];
};
static union task_union init_task = {INIT_TASK,};
long volatile jiffies=0;
long startup_time=0;
struct task_struct *current = &(init_task.task), *last_task_used_math =
NULL;
struct task_struct * task[NR_TASKS] = {&(init_task.task), };
long user_stack [ PAGE_SIZE>>2 ] ;
struct {
long * a;
short b;
} stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
/*
* 'math_state_restore()' saves the current math information in the
* old math state array, and gets the new ones from the current task
*/
void math_state_restore() @@協處理器狀態保存
{
if (last_task_used_math)
__asm__("fnsave %0"::"m" (last_task_used_math->tss.i387));
if (current->used_math)
__asm__("frstor %0"::"m" (current->tss.i387));
else {
__asm__("fninit"::);
current->used_math=1;
}
last_task_used_math=current;
}
/*
* 'schedule()' is the scheduler function. This is GOOD CODE! There
* probably won't be any reason to change this, as it should work well
* in all circumstances (ie gives IO-bound processes good response etc).
* The one thing you might take a look at is the signal-handler code
here.
*
* NOTE!! Task 0 is the 'idle' task, which gets called when no other
* tasks can run. It can not be killed, and it cannot sleep. The 'state'
* information in task[0] is never used.
*/
void schedule(void)
{
int i,next,c;
struct task_struct ** p;
/* check alarm, wake up any interruptible tasks that have got a signal
*/
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p) {
if ((*p)->alarm && (*p)->alarm < jiffies) {
@@??
(*p)->signal |= (1<<(SIGALRM-1));@@14-1
(*p)->alarm = 0;
}
if ((*p)->signal && (*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
}
@@ task 1 如何變爲TASK_RUNNING??signal 如何獲得,alarm如何變非0且 /* this is the
scheduler proper: */
@@操做系統最重要的函數,調度算法
@@這個循環要找到一個可運行的任務才能退出,會死在這嗎?即如沒有一個可運行
while (1) {
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
while (--i) {
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
if (c) break; @@記數大於零
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
}
switch_to(next);
}
int sys_pause(void)
{
current->state = TASK_INTERRUPTIBLE; @@任務可中斷
schedule();
return 0;
}
void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;
if (!p)
return;
if (current == &(init_task.task))
panic("task[0] trying to sleep");
tmp = *p;
*p = current;
current->state = TASK_UNINTERRUPTIBLE;
schedule();
if (tmp) @@激活p,何時回來?喚醒上次睡眠的進程
tmp->state=0;
}
void interruptible_sleep_on(struct task_struct **p)
{
struct task_struct *tmp;
if (!p)
return;
if (current == &(init_task.task))
panic("task[0] trying to sleep");
tmp=*p;
*p=current;
repeat: current->state = TASK_INTERRUPTIBLE;
schedule();
if (*p && *p != current) {
(**p).state=0;
goto repeat;
}
@@好象下不來
*p=NULL;
if (tmp)
tmp->state=0;
}
void wake_up(struct task_struct **p)
{
if (p && *p) {
(**p).state=0; @@喚醒該進程running
*p=NULL; @@睡眠棧爲0
}
}
void do_timer(long cpl) @@定時調度
{
if (cpl)
current->utime++; @@用戶態時間加一
else
current->stime++; @@系統態時間加一
if ((--current->counter)>0) return; @@當前記數減一
current->counter=0;
if (!cpl) return;
schedule();
}
int sys_alarm(long seconds)
{
current->alarm = (seconds>0)?(jiffies+HZ*seconds):0;
return seconds;
}
int sys_getpid(void)
{
return current->pid;
}
int sys_getppid(void)
{
return current->father;
}
int sys_getuid(void)
{
return current->uid;
}
int sys_geteuid(void)
{
return current->euid;
}
int sys_getgid(void)
{
return current->gid;
}
int sys_getegid(void)
{
return current->egid;
}
int sys_nice(long increment)
{
if (current->priority-increment>0)
current->priority -= increment;
return 0;
}
int sys_signal(long signal,long addr,long restorer)
{
long i;
switch (signal) {
case SIGHUP: case SIGINT: case SIGQUIT: case SIGILL:
case SIGTRAP: case SIGABRT: case SIGFPE: case SIGUSR1:
case SIGSEGV: case SIGUSR2: case SIGPIPE: case SIGALRM:
case SIGCHLD:
i=(long) current->sig_fn[signal-1];
current->sig_fn[signal-1] = (fn_ptr) addr;
current->sig_restorer = (fn_ptr) restorer;
return i;
default: return -1;
}
}
void sched_init(void)
{
int i;
struct desc_struct * p;
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));@@init task tss
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));@@init ldt
p = gdt+2+FIRST_TSS_ENTRY;
for(i=1;i task[i] = NULL;
p->a=p->b=0;
p++;
p->a=p->b=0;
p++;
}
ltr(0); @@調入task 0的tss
lldt(0); @@調入task 0的ldt
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */
set_intr_gate(0x20,&timer_interrupt); @@irq 0 時鐘中斷
outb(inb_p(0x21)&~0x01,0x21);
set_system_gate(0x80,&system_call);
}

[目錄]


進程信號隊列


每一個進程具備一個sigpending結構所描述的信號隊列,它有3個成員,head指向第一個sigqueu
e成員,tail指向最末的sigqueue成員的next指針,signal描述了此隊列中的信號集.

static int
send_signal(int sig, struct siginfo *info, struct sigpending *signals);
將信號sig和對應的消息結構info添加到信號隊列signal中.
static int
collect_signal(int sig, struct sigpending *list, siginfo_t *info);
返回信號sig在隊列list中的信息info.


struct task_struct {
        ...
        struct sigpending pending;
        ...
};
struct sigpending {
       struct sigqueue *head, **tail;
       sigset_t signal;
};
struct sigqueue {
       struct sigqueue *next;
       siginfo_t info;
       };
// kernel/signal.c
static int
send_signal(int sig, struct siginfo *info, struct sigpending *signals)
{
     struct sigqueue * q = NULL;
     /* Real-time signals must be queued if sent by sigqueue, or
       some other real-time mechanism.  It is implementation
      defined whether kill() does so.  We attempt to do so, on
      the principle of least surprise, but since kill is not
     allowed to fail with EAGAIN when low on memory we just
     make sure at least one signal gets delivered and don't
     pass on the info struct.  */

    if (atomic_read(&nr_queued_signals) < max_queued_signals) {
       q = kmem_cache_alloc(sigqueue_cachep, GFP_ATOMIC);
    }
    // nr_queued_signals和max_queued_signals用來限制全局sigqueue成員的數目
    if (q) {
        atomic_inc(&nr_queued_signals);
        q->next = NULL;
        *signals->tail = q;
        signals->tail = &q->next; tail老是指向最末的信號成員的next指針                switch ((unsign
ed long) info)
         {
        case 0:
          // info參數若是爲0,表示信號來源於當前用戶進程                                  q->info.si_signo =
sig;
            q->info.si_errno = 0;
            q->info.si_code = SI_USER;
          q->info.si_pid = current->pid;
            q->info.si_uid = current->uid;
          break;
        case 1:
          // info參數若是爲1,表示信號來源於內核自己                                  q->info.si_signo = sig;
          q->info.si_errno = 0;
          q->info.si_code = SI_KERNEL;
           q->info.si_pid = 0;
          q->info.si_uid = 0;
           break;
        default:
           // 不然從info指針中拷貝信號
           copy_siginfo(&q->info, info);
          break;
        }
      }
      else if (sig >= SIGRTMIN && info && (unsigned long)info != 1                   && info->
si_code != SI_USER)
      {
        ; 若是該信號是內核發出的實時信號,就返回錯誤碼
        /*
         * Queue overflow, abort.  We may abort if the signal was rt
         * and sent by user using something other than kill().
       */
        return -EAGAIN;
      }
      sigaddset(&signals->signal, sig); 將sig號標記在隊列的信號集上
      return 0;
}
static int
collect_signal(int sig, struct sigpending *list, siginfo_t *info)
{
      if (sigismember(&list->signal, sig)) {
        /* Collect the siginfo appropriate to this signal.  */                struct sigqueue *q, **
pp;
         pp = &list->head; pp指向第一個信號成員的next指針
        while ((q = *pp) != NULL) {
                if (q->info.si_signo == sig)                                            goto found_it;
                pp = &q->next;
         }
        /* Ok, it wasn't in the queue.  We must have
           been out of queue space.  So zero out the
          info.
         */
        sigdelset(&list->signal, sig);
        info->si_signo = sig;
        info->si_errno = 0;
        info->si_code = 0;
        info->si_pid = 0;
        info->si_uid = 0;
        return 1;
   found_it:
   // 將找到信號成員從信號隊列中刪除
        if ((*pp = q->next) == NULL)
        list->tail = pp;
  /* Copy the sigqueue information and free the queue entry */
       copy_siginfo(info, &q->info);
       kmem_cache_free(sigqueue_cachep,q);
       atomic_dec(&nr_queued_signals);
  /* Non-RT signals can exist multiple times.. */
       if (sig >= SIGRTMIN) {
                while ((q = *pp) != NULL) {
                  if (q->info.si_signo == sig)                                             goto found_another;
                    pp = &q->next;
                }
       }
       sigdelset(&list->signal, sig);
    found_another:
        return 1;
    }
    return 0;
}

[目錄]


SMP


   多處理機系統正在變得愈來愈普通。儘管大多數用戶空間代碼仍將完美地運行,並且
有些狀況下不須要增長額外的代碼就能利用SMP特性的優點,可是內核空間代碼必須編寫成
具有「SMP意識」且是「SMP安全的」。如下幾段文字解釋如何去作。

問題

    當有多個CPU時,一樣的代碼可能同時在兩個或多個CPU上執行。這在以下所示用於初
始化某個圖像設備的例程中可能會出問題。
        void init_hardware(void)
        {
            outb(0x1, hardware_base + 0x30);
            outb(0x2, hardware_base + 0x30);
            outb(0x3, hardware_base + 0x30);
            outb(0x4, hardware_base + 0x30);
        }
    假設該硬件依賴於寄存器0x30按順序依次被設爲0、一、二、3來初始化,那麼要是有另
一個CPU來參乎的話,事情就會搞糟。想象有兩個CPU的情形,它們都在執行這個例程,不
過2號CPU進入得稍慢點:
        CPU 1                           CPU 2

        0x30 = 1
        0x30 = 2                        0x30 = 1
        0x30 = 3                        0x30 = 2
        0x30 = 4                        0x30 = 3
                                        0x30 = 4
    這會發生什麼狀況呢?從咱們設想的硬件設備看來,它在寄存器0x30上收到的字節按
順序爲:一、二、一、三、二、四、三、4。
    啊!本來好好的事第二個CPU一來就搞得一團糟了也。所幸的是,咱們有防止這類事情
發生的辦法。

自旋鎖小歷史

    2.0.x版本的Linux內核經過給整個內核引入一個全局變量來防止多於一個CPU會形成的
問題。這意味着任什麼時候刻只有一個CPU可以執行來自內核空間的代碼。這樣儘管能工做,但
是當系統開始以多於2個的CPU出現時,擴展性能就不怎麼好。
    2.1.x版本的內核系列加入了粒度更細的SMP支持。這意味着再也不依賴於之前做爲全局
變量出現的「大鎖」,而是每一個沒有SMP意識的例程如今都須要各自的自旋鎖。文件asm/
spinlock.h中定義了若干類型的自旋鎖。
    有了局部化的自旋鎖後,不止一個CPU同時執行內核空間代碼就變得可能了。

簡單的自旋鎖

    理解自旋鎖的最簡單方法是把它做爲一個變量看待,該變量把一個例程或者標記爲
「我當前在另外一個CPU上運行,請稍等一會」,或者標記爲「我當前不在運行」。若是1號
CPU首先進入該例程,它就獲取該自旋鎖。當2號CPU試圖進入同一個例程時,該自旋鎖告訴
它本身已爲1號CPU所持有,需等到1號CPU釋放本身後才能進入。
        spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;
        unsigned long flags;

        spin_lock (&my_spinlock);
        ...
        critical section
        ...
        spin_unlock (&my_spinlock);

中斷

    設想咱們的硬件的驅動程序還有一箇中斷處理程序。該處理程序須要修改某些由咱們
的驅動程序定義的全局變量。這會形成混亂。咱們如何解決呢?
    保護某個數據結構,使它免遭中斷之修改的最初方法是全局地禁止中斷。在已知只有
本身的中斷纔會修改本身的驅動程序變量時,這麼作效率很低。所幸的是,咱們如今有更
好的辦法了。咱們只是在使用共享變量期間禁止中斷,此後從新使能。
    實現這種辦法的函數有三個:
        disable_irq()
        enable_irq()
        disable_irq_nosync()
    這三個函數都取一箇中斷號做爲參數。注意,禁止一箇中斷的時間太長會致使難以追
蹤程序缺陷,丟失數據,甚至更壞。
    disable_irq函數的非同步版本容許所指定的IRQ處理程序繼續運行,前提是它已經在
運行,普通的disable_irq則所指定的IRQ處理程序不在如何CPU上運行。
    若是須要在中斷處理程序中修改自旋鎖,那就不能使用普通的spin_lock()和
spin_unlock(),而應該保存中斷狀態。這可經過給這兩個函數添加_irqsave後綴很容易地
作到:
        spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;
        unsigned long flags;

        spin_lock_irqsave(&my_spinlock, flags);
        ...
        critical section
        ...
        spin_unlock_irqrestore (&my_spinlock, flags);

[目錄]


內核線程頁目錄的借用


建立內核線程的時候,因爲內核線程沒有用戶空間,而全部進程的內核頁目錄都是同樣的((某些狀況下可能有不一樣步的狀況出現,主要是爲了減輕同步全部進程內核頁目錄的開銷,而只是在各個進程要訪問內核空間,若是有不一樣步的狀況,而後才進?
同步處理),因此建立的內核線程的
內核頁目錄老是借用進程0的內核頁目錄。

>>> kernel_thread以標誌CLONE_VM調用clone系統調用
/*
* Create a kernel thread
*/
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
long retval, d0;

__asm__ __volatile__(
  "movl %%esp,%%esi/n/t"
  "int $0x80/n/t"  /* Linux/i386 system call */
  "cmpl %%esp,%%esi/n/t" /* child or parent? */
  /* Load the argument into eax, and push it.  That way, it does
   * not matter whether the called function is compiled with
   * -mregparm or not.  */
  "movl %4,%%eax/n/t"
  "pushl %%eax/n/t"
  "call *%5/n/t"  /* call fn */
  "movl %3,%0/n/t" /* exit */
  "int $0x80/n"
  "1:/t"
  :"=&a" (retval), "=&S" (d0)
  :"0" (__NR_clone), "i" (__NR_exit),
   "r" (arg), "r" (fn),
   "b" (flags | CLONE_VM)
  : "memory");
return retval;
}

>>> sys_clone->do_fork->copy_mm:
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{
struct mm_struct * mm, *oldmm;
int retval;

。。。。。。。。

tsk->mm = NULL;
tsk->active_mm = NULL;

/*
  * Are we cloning a kernel thread?
  *
  * We need to steal a active VM for that..
  */
>>> 若是是內核線程的子線程(mm=NULL),則直接退出,此時內核線程mm和active_mm均爲爲NULL
oldmm = current->mm;
if (!oldmm)
  return 0;

>>> 內核線程,只是增長當前進程的虛擬空間的引用計數
if (clone_flags & CLONE_VM) {
  atomic_inc(&oldmm->mm_users);
  mm = oldmm;
  goto good_mm;
}

。。。。。。。。。。

good_mm:
>>> 內核線程的mm和active_mm指向當前進程的mm_struct結構
tsk->mm = mm;
tsk->active_mm = mm;
return 0;

。。。。。。。
}

而後內核線程通常調用daemonize來釋放對用戶空間的引用:
>>> daemonize->exit_mm->_exit_mm:
/*
* Turn us into a lazy TLB process if we
* aren't already..
*/
static inline void __exit_mm(struct task_struct * tsk)
{
struct mm_struct * mm = tsk->mm;

mm_release();
if (mm) {
  atomic_inc(&mm->mm_count);
  if (mm != tsk->active_mm) BUG();
  /* more a memory barrier than a real lock */
  task_lock(tsk);
>>> 釋放用戶虛擬空間的數據結構
  tsk->mm = NULL;
  task_unlock(tsk);
  enter_lazy_tlb(mm, current, smp_processor_id());

>>> 遞減mm的引用計數並是否爲0,是則釋放mm所表明的映射
  mmput(mm);
}
}

asmlinkage void schedule(void)
{
。。。。。。。。。
if (!current->active_mm) BUG();

。。。。。。。。。

prepare_to_switch();
{
  struct mm_struct *mm = next->mm;
  struct mm_struct *oldmm = prev->active_mm;
>>> mm = NULL,選中的爲內核線程
  if (!mm) {
>>> 對內核線程,active_mm = NULL,不然必定是出錯了
   if (next->active_mm) BUG();
>>> 選中的內核線程active_mm借用老進程的active_mm
   next->active_mm = oldmm;
   atomic_inc(&oldmm->mm_count);
   enter_lazy_tlb(oldmm, next, this_cpu);
  } else {
>>> mm != NULL 選中的爲用戶進程,active_mm必須與mm相等,不然必定是出錯了
   if (next->active_mm != mm) BUG();
   switch_mm(oldmm, mm, next, this_cpu);
  }

>>> prev = NULL ,切換出去的是內核線程
  if (!prev->mm) {
>>> 設置其 active_mm = NULL 。
   prev->active_mm = NULL;
   mmdrop(oldmm);
  }
}

}

對內核線程的虛擬空間總結一下:
1、建立的時候:
父進程是用戶進程,則mm和active_mm均共享父進程的,而後內核線程通常調用daemonize適頭舖m
父進程是內核線程,則mm和active_mm均爲NULL
總之,內核線程的mm = NULL;進程調度的時候以此爲依據判斷是用戶進程仍是內核線程。

2、進程調度的時候
若是切換進來的是內核線程,則置active_mm爲切換出去的進程的active_mm;
若是切換出去的是內核線程,則置active_mm爲NULL。

[目錄]


代碼分析


LINUX系統是分時多用戶系統, 它有多進程系統的特色,CPU按時間片分配給各個用戶使用, 而在實質上應該說CPU按時間片分配給各個進程使用, 每一個進程都有本身的運行環境以使得在CPU作進程切換時保存該進程已計算了一半的狀態。
進程的切換包括三個層次:
用戶數據的保存: 包括正文段(TEXT), 數據段(DATA,BSS), 棧段(STACK), 共享內存段(SHARED MEMORY)的保存。
寄存器數據的保存: 包括PC(program counter,指向下一條要執行的指令的地址),   PSW(processor status word,處理機狀態字), SP(stack pointer,棧指針), PCBP(pointer of process control block,進程控制塊指針), FP(frame pointer,指向棧中一個函數的local 變量的首地址), AP(augument pointer,指向棧中函數調用的實參位置), ISP(interrupt stack pointer,中斷棧指針), 以及其餘的通用寄存器等。
系統層次的保存: 包括proc,u,虛擬存儲空間管理表格,中斷處理棧。以便於該進程再一次獲得CPU時間片時能正常運行下去。

    多進程系統的一些突出的特色:
並行化
   一件複雜的事件是能夠分解成若干個簡單事件來解決的, 這在程序員的大腦中早就造成了這種概念, 首先將問題分解成一個個小問題, 將小問題再細分, 最後在一個合適的規模上作成一個函數。 在軟件工程中也是這麼說的。若是咱們以圖的方式來思考, 一些小問題的計算是能夠互不干擾的, 能夠同時處理, 而在關鍵點則須要統一在一個地方來處理, 這樣程序的運行就是並行的, 至少從人的時間觀念上來講是這樣的。 而每一個小問題的計算又是較簡單的。
簡單有序
   這樣的程序對程序員來講不亞於管理一班人, 程序員爲每一個進程設計好相應的功能, 並經過必定的通信機制將它們有機地結合在一塊兒, 對每一個進程的設計是簡單的, 只在總控部分當心應付(其實也是蠻簡單的), 就可完成整個程序的施工。
互不干擾
   這個特色是操做系統的特色, 各個進程是獨立的, 不會串位。
事務化
   好比在一個數據電話查詢系統中, 將程序設計成一個進程只處理一次查詢便可, 即完成一個事務。當電話查詢開始時, 產生這樣一個進程對付此次查詢; 另外一個電話進來時, 主控程序又產生一個這樣的進程對付, 每一個進程完成查詢任務後消失. 這樣的編程多簡單, 只要作一次查詢的程序就能夠了。

   Linux是一個多進程的操做系統,進程是分離的任務,擁有各自的權利和責任。若是一個進程崩潰,它不該該讓系統的另外一個進程崩潰。每個獨立的進程運行在本身的虛擬地址空間,除了經過安全的核心管理的機制以外沒法影響其餘的進程。
   在一個進程的生命週期中,進程會使用許多系統資源。好比利用系統的CPU執行它的指令,用系統的物理內存來存儲它和它的數據。它會打開和使用文件系統中的文件,會直接或者間接使用系統的物理設備。若是一個進程獨佔了系統的大部分物理內存和CPU,對於其餘進程就是不公平的。因此Linux必須跟蹤進程自己和它使用的系統資源以便公平地管理系統中的進程。
   系統最寶貴的資源就是CPU。一般系統只有一個CPU。Linux做爲一個多進程的操做系統,它的目標就是讓進程在系統的CPU上運行,充分利用CPU。若是進程數多於CPU(通常狀況都是這樣),其餘的進程就必須等到CPU被釋放才能運行。多進程的思想就是:一個進程一直運行,直到它必須等待,一般是等待一些系統資源,等擁有了資源,它才能夠繼續運行。在一個單進程的系統中,好比DOS,CPU被簡單地設爲空閒,這樣等待資源的時間就會被浪費。而在一個多進程的系統中,同一時刻許多進程在內存中,當一個進程必須等待時,操做系統將CPU從這個進程切換到另外一個更須要的進程。
   咱們組分析的是Linux進程的狀態轉換以及標誌位的做用,它沒有具體對應某個系統調用,而是分佈在各個系統調用中。因此咱們詳細而普遍地分析了大量的原碼,對進程狀態轉換的緣由、方式和結果進行了分析,大體總結了整個Linux系統對進程狀態管理的實現機制。

   Linux中,每一個進程用一個task_struct的數據結構來表示,用來管理系統中的進程。Task向量表是指向系統中每個task_struct數據結構的指針的數組。這意味着系統中的最大進程數受到Task向量表的限制,缺省是512。這個表讓Linux能夠查到系統中的全部的進程。操做系統初始化後,創建了第一個task_struct數據結構INIT_TASK。當新的進程建立時,從系統內存中分配一個新的task_struct,並增長到Task向量表中。爲了更容易查找,用current指針指向當前運行的進程。

   task_struct結構中有關於進程調度的兩個重要的數據項:
   struct task_struct {
       ………….
       volatile  long  state; /* -1 unrunnable , 0 runnable , >0 stopped */
       unsigned  long  flags; /* per process flags, defined below */
       ………….
     };
   每一個在Task向量表中登記的進程都有相應的進程狀態和進程標誌,是進行進程調度的重要依據。進程在執行了相應的進程調度操做後,會因爲某些緣由改變自身的狀態和標誌,也就是改變state和flags這兩個數據項。進程的狀態不一樣、標誌位不一樣對應了進程能夠執行不一樣操做。在Linux2.2.8版本的sched.h中定義了六種狀態,十三種標誌。
//進程狀態
#define TASK_RUNNING                0
#define TASK_INTERRUPTIBLE        1
#define TASK_UNINTERRUPTIBLE        2
#define TASK_ZOMBIE                4
#define TASK_STOPPED                8
#define TASK_SWAPPING                16

它們的含義分別是:

TASK_RUNNING:正在運行的進程(是系統的當前進程)或準備運行的進程(在Running隊列中,等待被安排到系統的CPU)。處於該狀態的進程實際參與了進程調度。
TASK_INTERRUPTIBLE:處於等待隊列中的進程,待資源有效時喚醒,也可由其它進程被信號中斷、喚醒後進入就緒狀態。
TASK_UNINTERRUPTIBLE:處於等待隊列中的進程,直接等待硬件條件,待資源有效時喚醒,不可由其它進程經過信號中斷、喚醒。
TASK_ZOMBIE:終止的進程,是進程結束運行前的一個過分狀態(僵死狀態)。雖然此時已經釋放了內存、文件等資源,可是在Task向量表中仍有一個task_struct數據結構項。它不進行任何調度或狀態轉換,等待父進程將它完全釋放。
TASK_STOPPED:進程被暫停,經過其它進程的信號才能喚醒。正在調試的進程能夠在該中止狀態。
TASK_SWAPPING:進程頁面被兌換出內存的進程。這個狀態基本上沒有用到,只有在sched.c的count_active_tasks()函數中判斷處於該種狀態的進程也屬於active的進程,但沒有對該狀態的賦值。

//進程標誌位:
#define PF_ALIGNWARN        0x00000001
#define PF_STARTING        0x00000002
#define PF_EXITING        0x00000004
#define PF_PTRACED        0x00000010
#define PF_TRACESYS        0x00000020
#define PF_FORKNOEXEC        0x00000040
#define PF_SUPERPRIV        0x00000100
#define PF_DUMPCORE        0x00000200
#define PF_SIGNALED        0x00000400
#define PF_MEMALLOC        0x00000800
#define PF_VFORK            0x00001000
#define PF_USEDFPU        0x00100000
#define PF_DTRACE        0x00200000

其中PF_STARTING沒有用到。
PF_MEMEALLOC和PF_VFORK這兩個標誌位是新版本中才有的。

各個標誌位的表明着不一樣含義,對應着不一樣調用:

PF_ALIGNWARN    標誌打印「對齊」警告信息,只有在486機器上實現
PF_STARTING      進程正被建立
PF_EXITING        標誌進程開始關閉。
在do_exit()時置位。
    current->flags |= PF_EXITING
用於判斷是否有效進程。
在nlmclnt_proc()(在fs/lockd/clntproc.c),若是current_flag爲PF_EXITING,則進程因爲正在退出清除全部的鎖,將執行異步RPC 調用。
PF_PTRACED      進程被跟蹤標誌,
在do_fork()時清位。
    p->flags &= ~PF_PTRACED
當ptrace(0)被調用時置位,在進程釋放前要清掉。
    current->flags |= PF_PTRACED
在sys_trace()中判斷
若是request爲PTRACE_TRACEME,如是則將current_flag置爲PF_PTRACED;
若是request爲PTRACE_ATTACH,則將child_flag置爲PF_PTRACED,給child發一個SIGSTOP信號;
若是request爲PTRACE_DETACH ,則將child清除PF_PTRACED。
在syscall_trace()中判斷current_flag若是爲PF_TRACED和PF_TRACESYS,則current強行退出時的出錯代碼置爲SIGTRAP並將狀態置爲STOPPED。
PF_TRACESYS       正在跟蹤系統調用。
do_fork()時清位,在進程釋放前要清掉。
在sys_trace()中判斷request若是爲PTRACE_SYSCALL,則將child->flags 置爲 PF_TRACESYS;如爲PTRACE_SYSCALL,則將child->flags 清除 PF_TRACESYS;而後喚醒child。若是request爲PTRACE_SINGLESTEP(即單步跟蹤),則將child_flag清除PF_TRACESYS,喚醒child。
PF_FORKNOEXEC    進程剛建立,但還沒執行。
在do_fork()時置位。
    p->flags |=  PF_FORKNOEXEC
在調入格式文件時清位。
    p->flags &= ~ PF_FORKNOEXEC
PF_SUPERPRIV       超級用戶特權標誌。
    若是是超級用戶進程則置位,用戶特權設爲超級用戶,如是超級用戶,在統計時置統計標誌(accounting flag)爲ASU。
PF_DUMPCORE       標誌進程是否清空core文件。
Core文件由gdb進行管理,給用戶提供有用信息,例如查看浮點寄存器的內容比較困難,事實上咱們能夠從內核文件裏的用戶結構中獲得
  Core文件格式以下圖:
UPAGE
DATA
STACK
              Core 文件結構
       UPAGE是包含用戶結構的一個頁面,告訴gdb文件中現有內容全部寄存器也在           UPAGE中,一般只有一頁。DATA存放數據區。STACK堆棧區
   最小Core文件長度爲三頁(12288字節)
在task_struct中定義一個dumpable變量,當dumpable==1時表示進程能夠清空core文件(即將core文件放入回收站),等於0時表示該進程不能清空core文件(即core文件以放在回收站中,不可再放到回收站中),此變量初值爲1。
例如在調用do_aout_core_dump()時判斷current->dumpable是否等於1(即判斷該進程是否能將core文件放入回收站),若是等於1則將該變量置爲0,在當前目錄下創建一個core dump image ,在清空用戶結構前,由gdb算出數據段和堆棧段的位置和使用的虛地址,用戶數據區和堆棧區在清空前將相應內容寫入core dump,將PF_DUMPCORE置位,清空數據區和堆棧區。
只有在aout_core_dump()內調用do_aout_core_dump(),而沒有地方調用aout_core_dump()。對其它文件格式也是相似。
九、 PF_SIGNALED       標誌進程被信號殺出。
在do_signal()中判斷信號,若是current收到信號爲SIGHUP, SIGINT, SIGIOT, SIGKILL, SIGPIPE, SIGTERM, SIGALRM, SIGSTKFLT, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGIO, SIGPOLL, SIGLOST, SIGPWR,則執行lock_kernel(),將信號加入current的信號隊列,將current->flag置爲PF_SIGNALED,而後執行do_exit()
PF_USEDFPU        標誌該進程使用FPU,此標誌只在SMP時使用。
在task_struct中有一變量used_math,進程是否使用FPU。
在CPU從prev切換到next時,若是prev使用FPU則prev的flag清除PF_USEDFPU。
    prev->flags&=~PF_USEDFPU
在flush_thread()(arch/i386/kernel/process.c)、restore_i387_hard()、save_i387_hard()(arch/i386/kernel/signal.c)中,若是是SMP方式,且使用FPU則stts(),不然清除PF_USEDFPU。
    current->flags &= ~PF_USEDFPU
在sys_trace()中若是request爲PTRACE_SETFPREGS,則將child的used_math置爲1,將child_flag清除PF_USEDFPU。
    child->flags &= ~PF_USEDFPU
在SMP方式下進行跟蹤時,判斷是否使用FPU。
在跟蹤時出現數學錯誤時清位。
    current->flags &= ~PF_USEDFPU
PF_DTRACE          進程延期跟蹤標誌,只在m68k下使用。
跟蹤一個trapping指令時置位。
    current->flags |= PF_DTRACE
PF_ONSIGSTK        標誌進程是否工做在信號棧,只在m68k方式下使用。
liunx 2.1.19版本中使用此標誌位,而2.2.8版本中不使用。
在處理信號創建frame時若是sigaction標誌爲ONSTACK,則將current->flag置爲PF_ONSIGSTK。
PF_MEMALLOC      進程分配內存標誌。
linux 2.2.8版本中使用此標誌位。
在kpiod()和kwpad()中置位。
    tsk->flags |= PF_MEMALLOC
PF_VFORK           linux 2.2.8版本中使用此標誌位。
在copy_flags(unsigned long clone_flags, struct task_struct *p),若是clone_flags爲CLONE_VFORK,則將p的flags置爲PF_VFORK。
在mm_release()中將current ->flags清除PF_VFORK。
    tsk->flags &= ~PF_VFORK
    具體的分析由我組的另外同窗進行。

Linux的各進程之間的狀態轉換的系統調用
我將參與Linux的各進程之間的狀態轉換的系統調用總結成一張流程圖:

 

進程的建立:TASK_RUNNING

第一個進程在系統啓動時建立,當系統啓動的時候它運行在覈心態,這時,只有一個進程:初始化進程。象全部其餘進程同樣,初始進程有一組用堆棧、寄存器等等表示的機器狀態。當系統中的其餘進程建立和運行的時候這些信息存在初始進程的task_struct數據結構中。在系統初始化結束的時候,初始進程啓動一個核心進程(叫作init)而後執行空閒循環,什麼也不作。當沒有什麼能夠作的時候,調度程序會運行這個空閒的進程。這個空閒進程的task_struct是惟一一個不是動態分配而是在覈心鏈接的時候靜態定義的,爲了避免至於混淆,叫作init_task。
   系統調用sys_fork 和sys_clone都調用函數do_fork()(在kernel/fork.中定義)。
   進程由do_fork()函數建立,先申請空間,申請核心堆棧;而後在Task向量表中找到空閒位置;在進行正式初始化之前,將新建立的進程的狀態都置爲TASK_UNINTERRUPTIBLE,以避免初始化過程被打斷;開始初始化工做,如初始化進程時鐘、信號、時間等數據;繼承父進程的資源,如文件、信號量、內存等;完成進程初始化後,由父進程調用wake_up_process()函數將其喚醒,狀態變爲TASK_RUNNING,掛到就緒隊列run queue,返回子進程的pid。

//   C:/SRCLNX/KERNEL/FORK.C
int do_fork(unsigned long clone_flags, unsigned long usp, struct pt_regs *regs)
{

        爲新進程申請PCB空間;
        if (申請不到)
                返回錯誤,退出;
        爲新進程申請核心堆棧;
        if (核心堆棧申請不到)
                返回錯誤,退出;
        爲新進程在Task向量表中找到空閒位置;
/*複製父進程current PCB中的信息,繼承current的資源*/;
    p = current;
        在進行正式初始化之前,將新建立的進程的狀態都置爲TASK_UNINTERRUPTIBLE,以避免初始化過程被打斷,並置一些標誌位.
/*爲防止信號、定時中斷誤喚醒未建立完畢的進                                       程,將子進程的狀態設成不可中斷的*/
        p->state = TASK_UNINTERRUPTIBLE;
/*跟蹤狀態和超級用戶特權是沒有繼承性的,由於在root用戶爲普通用戶建立進程時,出於安全考慮這個普通用戶的進程不容許擁有超級用戶特權。*/
        p->flags &= ~(PF_PTRACED|PF_TRACESYS|PF_SUPERPRIV);
/*將進程標誌設成初建,在進程第一次得到CPU時,內核將根據此標誌進行必定操做*/
        p->flags |= PF_FORKNOEXEC;
   開始Task_struct的初始化工做,如初始化進程時鐘、信號、時間等數據;
   繼承父進程全部資源:
                拷貝父進程當前打開的文件;
                拷貝父進程在VFS的位置;
                拷貝父進程的信號量;
                拷貝父進程運行的內存;
                拷貝父進程的線程;
   初始化工做結束,父進程將其將其喚醒,掛入running隊列中,返回子進程的pid;

}

進程的調度(schedule()):

   處於TASK_RUNNING狀態的進程移到run queue,會由schedule()按CPU調度算法在合適的時候選中,分配給CPU。
   新建立的進程都是處於TASK_RUNNING狀態,並且被掛到run queue的隊首。進程調度採用變形的輪轉法(round robin)。當時間片到時(10ms的整數倍),由時鐘中斷引發新一輪調度,把當前進程掛到run queue隊尾。
   全部的進程部分運行與用戶態,部分運行於系統態。底層的硬件如何支持這些狀態各不相同可是一般有一個安全機制從用戶態轉入系統態並轉回來。用戶態比系統態的權限低了不少。每一次進程執行一個系統調用,它都從用戶態切換到系統態並繼續執行。這時讓核心執行這個進程。Linux中,進程不是互相爭奪成爲當前運行的進程,它們沒法中止正在運行的其它進程而後執行自身。每個進程在它必須等待一些系統事件的時候會放棄CPU。例如,一個進程可能不得不等待從一個文件中讀取一個字符。這個等待發生在系統態的系統調用中。進程使用了庫函數打開並讀文件,庫函數又執行系統調用從打開的文件中讀入字節。這時,等候的進程會被掛起,另外一個更加值得的進程將會被選擇執行。進程常常調用系統調用,因此常常須要等待。即便進程執行到須要等待也有可能會用去不均衡的CPU事件,因此Linux使用搶先式的調度。用這種方案,每個進程容許運行少許一段時間,200毫秒,當這個時間過去,選擇另外一個進程運行,原來的進程等待一段時間直到它又從新運行。這個時間段叫作時間片。
   須要調度程序選擇系統中全部能夠運行的進程中最值得的進程。一個能夠運行的進程是一個只等待CPU的進程。Linux使用合理而簡單的基於優先級的調度算法在系統當前的進程中進行選擇。當它選擇了準備運行的新進程,它就保存當前進程的狀態、和處理器相關的寄存器和其餘須要保存的上下文信息到進程的task_struct數據結構中。而後恢復要運行的新的進程的狀態(又和處理器相關),把系統的控制交給這個進程。爲了公平地在系統中全部能夠運行(runnable)的進程之間分配CPU時間,調度程序在每個進程的task_struct結構中保存了信息。
   policy 進程的調度策略:Linux有兩種類型的進程:普通和實時。實時進程比全部其它進程的優先級高。若是有一個實時的進程準備運行,那麼它老是先被運行。實時進程有兩種策略:環或先進先出(round robin and first in first out)。在環的調度策略下,每個實時進程依次運行,而在先進先出的策略下,每個能夠運行的進程按照它在調度隊列中的順序運行,這個順序不會改變。
   Priority 進程的調度優先級。也是它容許運行的時候能夠使用的時間量(jiffies)。你能夠經過系統調用或者renice命令來改變一個進程的優先級。
   Rt_priority Linux支持實時進程。這些進程比系統中其餘非實時的進程擁有更高的優先級。這個域容許調度程序賦予每個實時進程一個相對的優先級。實時進程的優先級能夠用系統調用來修改Coutner 這時進程能夠運行的時間量(jiffies)。進程啓動的時候等於優先級(priority),每一次時鐘週期遞減。
  調度程序schedule()從核心的多個地方運行。它能夠在把當前進程放到等待隊列以後運行,也能夠在系統調用以後進程從系統態返回進程態以前運行。須要運行調度程序的另外一個緣由是系統時鐘恰好把當前進程的計數器(counter)置成了0。每一次調度程序運行它作如下工做:
(1)kernel work 調度程序運行bottom half handler並處理系統的調度任務隊列。
(2)Current pocess 在選擇另外一個進程以前必須處理當前進程。
(3)若是當前進程的調度策略是環則它放到運行隊列的最後。
(4)若是任務狀態是TASK_INTERRUPTIBLE的並且它上次調度的時候收到過一個信號,它的狀態變爲TASK_RUNNING;
     若是當前進程超時,它的狀態成爲RUNNING;
     若是當前進程的狀態爲RUNNING則保持此狀態;
     不是RUNNING或者INTERRUPTIBLE的進程被從運行隊列中刪除。這意味着當調度程序查找最值得運行的進程時不會考慮這樣的進程。
(5)Process Selection 調度程序查看運行隊列中的進程,查找最值得運行的進程。若是有實時的進程(具備實時調度策略),就會比普通進程更重一些。普通進程的重量是它的counter,可是對於實時進程則是counter 加1000。這意味着若是系統中存在可運行的實時進程,就老是在任何普通可運行的進程以前運行。當前的進程,由於用掉了一些時間片(它的counter減小了),因此若是系統中由其餘同等優先級的進程,就會處於不利的位置:這也是應該的。若是幾個進程又一樣的優先級,最接近運行隊列前段的那個就被選中。當前進程被放到運行隊列的後面。若是一個平衡的系統,擁有大量相同優先級的進程,那麼回按照順序執行這些進程。這叫作環型調度策略。不過,由於進程須要等待資源,它們的運行順序可能會變化。
(6)Swap Processes 若是最值得運行的進程不是當前進程,當前進程必須被掛起,運行新的進程。當一個進程運行的時候它使用了CPU和系統的寄存器和物理內存。每一次它調用例程都經過寄存器或者堆棧傳遞參數、保存數值好比調用例程的返回地址等。所以,當調度程序運行的時候它在當前進程的上下文運行。它多是特權模式:核心態,可是它仍舊是當前運行的進程。當這個進程要掛起時,它的全部機器狀態,包括程序計數器(PC)和全部的處理器寄存器,必須存到進程的task_struct數據結構中。而後,必須加載新進程的全部機器狀態。這種操做依賴於系統,不一樣的CPU不會徹底相同地實現,不過常常都是經過一些硬件的幫助。
(7)交換出去進程的上下文發生在調度的最後。前一個進程存儲的上下文,就是當這個進程在調度結束的時候系統的硬件上下文的快照。相同的,當加載新的進程的上下文時,仍舊是調度結束時的快照,包括進程的程序計數器和寄存器的內容。
(8)若是前一個進程或者新的當前進程使用虛擬內存,則系統的頁表須要更新。一樣,這個動做適合體系結構相關。Alpha AXP處理器,使用TLT(Translation Look-aside Table)或者緩存的頁表條目,必須清除屬於前一個進程的緩存的頁表條目。

   下面我就來總結一下進程建立之後到被殺死的整個進程生命週期中,狀態可能在TASK_RUNNING、TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE 、TASK_STOPPED以及TASK_ZOMBLE之間轉換的緣由。

進程在TASK_RUNNING以及TASK_UNINTERRUPTIBLE、TASK_INTERRUPTIBLE之間轉換:
   得到CPU而正在運行的進程會因爲某些緣由,好比:申請不到某個資源,其狀態會從TASK_RUNNING變爲TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE的等待狀態。一樣在經歷了某些狀況,處於等待狀態的進程會被從新喚醒,等待分配給CPU。狀態爲TASK_INTERRUPTIBLE的睡眠進程會被喚醒,回到TASK_RUNNING狀態,從新等待schedule()分配給它CPU,繼續運行,好比:當申請資源有效時,也能夠由signal或定時中斷喚醒。而狀態爲TASK_INTERRUPTIBLE的睡眠進程只有當申請資源有效時被喚醒,不能被signal、定時中斷喚醒。

1.經過sleep_on()、interruptible_sleep_on()、sleep_on_timeout()、interruptible_sleep_on_timeout()以及wake_up()、wake_up_process()、wake_up_interruptible()函數對進行的轉換:

   sleep_on():TASK_RUNNING->TASK_UNINTERRUPTIBLE
   當擁有CPU的進程申請資源無效時,會經過sleep_on(),將進程從TASK_RUNNING切換到TASK_UNINTERRUPTIBLE狀態。sleep_on()函數的做用就是將current進程的狀態置成TASK_UNINTERRUPTIBLE,並加到等待隊列中。
   通常來講引發狀態變成TASK_UNINTERRUPTIBLE的資源申請都是對一些硬件資源的申請,若是得不到這些資源,進程將不能執行下去,不能由signal信號或時鐘中斷喚醒,而回到TASK_RUNNING狀態。
   咱們總結了這種類型的轉換緣由有:
(1)對某些資源的操做只能由一個進程進行,因此係統對該項資源採用上鎖機制。在申請該項資源時,必須先申請資源的鎖,若是已經被別的進程佔用,則必須睡眠在對該鎖的等待隊列上。並且這種睡眠不能被中斷,必須等到獲得了資源才能繼續進行下去。
如:
對網絡鏈接表鎖(Netlink table lock)的申請, sleep_on(&nl_table_wait);
對交換頁進行I/O操做的鎖的申請, sleep_on(&lock_queue);
對Hash表操做的鎖的申請, sleep_on(&hash_wait);
在UMSDOS文件系統建立文件或目錄時,必須等待其餘一樣的建立工做結束,sleep_on (&dir->u.umsdos_i.u.dir_info.p);

(2)某些進程在大部分時間處於睡眠狀態,僅在須要時被喚醒去執行相應的操做,當執行完後,該進程又強制去睡眠。
如:
wakeup_bdflush()是對dirty buffer進行動態的響應,一旦該進程被激活,就將必定數量的dirty buffer寫回磁盤,而後調用sleep_on(&bdflush_done),又去睡眠。

interruptible_sleep_on():TASK_RUNNING->TASK_INTERRUPTIBLE
   與sleep_on()函數很是地相象,當擁有CPU的進程申請資源無效時,會經過interruptible_sleep_on(),將進程從TASK_RUNNING切換到TASK_INTERRUPTIBLE狀態。interruptible_sleep_on()函數的做用就是將current進程的狀態置成TASK_INTERRUPTIBLE,並加到等待隊列中。
   處於TASK_INTERRUPTIBLE狀態的進程能夠在資源有效時被wake_up()、wake_up_interruptible()或wake_up_process()喚醒,或收到signal信號以及時間中斷後被喚醒。
   進行這種轉換的緣由基本上與sleep_on()相同,申請資源無效時進程切換到等待狀態。與之不一樣的是處於interruptible_sleep_on()等待狀態的進程是能夠接受信號或中斷而從新變爲running狀態。因此能夠認爲對這些資源的申請沒有象在sleep_on()中資源的要求那麼嚴格,必須獲得該資源進程才能繼續其運行下去。

sleep_on_timeout():TASK_RUNNING->TASK_UNINTERRUPTIBLE
sleep_on_timeout(&block.b_wait, 30*HZ);

interruptible_sleep_on_timeout():TASK_RUNNING->TASK_INTERRUPTIBLE
   雖然在申請資源或運行中出現了某種錯誤,可是系統仍然給進程一次從新運行的機會。調用該函數將進程從TASK_RUNNING切換到TASK_INTERRUTIBLE狀態,並等待規定的時間片長度,再從新試一次。
如:在smb_request_ok 中產生了鏈接失敗的錯誤,會在sem_retry()中給一次從新鏈接的機會。//interruptible_sleep_on_timeout(&server->wait,  5*HZ);

wake_up():TASK_UNINTERRUPTIBLE-> TASK_RUNNING;
          TASK_INTERRUPTIBLE-> TASK_RUNNING
   處於TASK_UNINTERRUPTIBLE狀態的進程不能由signal信號或時鐘中斷喚醒,只能由wake_up()或wake_up_process()喚醒。wake_up()函數的做用是將wait_queue中的全部狀態爲TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE的進程狀態都置爲TASK_RUNNING,並將它們都放到running隊列中去,即喚醒了全部等待在該隊列上的進程。
void wake_up(struct wait_queue **q)
{
        struct wait_queue *next;
        struct wait_queue *head;

        if (!q || !(next = *q))
                return;
        head = WAIT_QUEUE_HEAD(q);
        while (next != head) {
                struct task_struct *p = next->task;
                next = next->next;
                if (p != NULL) {
                        if ((p->state == TASK_UNINTERRUPTIBLE) ||
                            (p->state == TASK_INTERRUPTIBLE))
                                wake_up_process(p);
                }
                if (!next)
                        goto bad;
        }
        return;
bad:
        printk("wait_queue is bad (eip = %p)/n",
                __builtin_return_address(0));
        printk("        q = %p/n",q);
        printk("       *q = %p/n",*q);
}

  wake_up()在下列狀況下被調用:
這個函數一般在資源有效時調用,資源鎖已經被釋放,等待該資源的全部進程都被置爲TASK_RUNNING狀態,移到run queue,從新參與調度,對這一資源再次競爭。這時又會有某個進程競爭到了該項資源,而其餘的進程在申請失敗後,又回到TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE狀態。
如:
網絡鏈接表鎖(Netlink table lock)釋放後,喚醒等待該鎖的全部睡眠進程 wake_up(&nl_table_wait);
對交換頁進行I/O操做的鎖釋放後,喚醒等待該鎖的全部睡眠進程, wake_up(&lock_queue);
對Hash表操做的鎖釋放後,喚醒等待該鎖的全部睡眠進程,wake_up(&hash_wait);
在UMSDOS文件系統建立文件或目錄工做結束後,喚醒其餘因爲等待它建立結束而睡眠的進程, wake_up (&dir->u.umsdos_i.u.dir_info.p);

喚醒睡眠進程執行某些操做:
如:
bd_flush()函數要將一些dirty buffer寫回磁盤,就調用wake_up(&bdflush_done),喚醒正在睡眠的wakeup_bdflush()進程去處理寫回。


wake_up_process():TASK_UNINTERRUPTIBLE-> TASK_RUNNING;
                   TASK_INTERRUPTIBLE-> TASK_RUNNING
   wake_up_process()函數的做用是將參數所指的那個進程狀態從TASK_INTERRUPTIBLE,TASK_UNINTERRUPTIBLE變爲TASK_RUNNING,並將它放到running隊列中去。

void wake_up_process(struct task_struct * p)
{
        unsigned long flags;

        /*
        * We want the common case fall through straight, thus the goto.
        */
        spin_lock_irqsave(&runqueue_lock, flags);
        p->state = TASK_RUNNING;
        if (p->next_run)
                goto out;
        add_to_runqueue(p);
        spin_unlock_irqrestore(&runqueue_lock, flags);

        reschedule_idle(p);
        return;
out:
        spin_unlock_irqrestore(&runqueue_lock, flags);
}

   這個函數的實現機制與wake_up()的不一樣在於,它只能喚醒某一個特定的睡眠進程,而wake_up()是喚醒整個等待隊列的睡眠進程。因此,它的喚醒的緣由與wake_up()也有必定的區別,除了因爲wake_up()對它的調用以外,它喚醒進程並非因爲資源有效形成的,喚醒的進程也不是因等待資源有效而睡眠的進程。有如下幾種狀況:
父進程對子進程的喚醒:
如:
在sys_ptrace()中當收到的跟蹤請求爲:PTRACE_CONT(在處理完信號後繼續);PTRACE_KILL(將子進程殺出);PTRACE_SINGLESTEP(對子進程進行單步跟蹤);PTRACE_DETACH的時候,都會在處理結束時,喚醒子進程,給子進程一個運行的機會。
在do_fork()中,新建進程初始化完畢,會由父進程喚醒它,將該進程移到run queue中,置狀態爲TASK_RUNNING。

當須要的時候喚醒某個睡眠的系統調用,進行處理:
如:
kswapd_process頁面交換進程,一般是處於睡眠狀態的,當某個進程須要更多的內存,而調用try_to_free_pages()時,就會喚醒kswapd_process頁面交換進程,調入更多的內存頁面。

收到信號所進行的相應處理:
如:
某一進程的時間片到了,process_timeout()會調用wake_up_process()喚醒該進程;
收到某些signal信號:處於STOPPED狀態的進程收到SIGKILL或SIGCONT會被喚醒(注:處於STOPPED狀態的進程不能被wake_up()喚醒);以及收到某些非實時信號,不需加到signal隊列中去,處於TASK_INTERRUPTIBLE的進程有機會被喚醒。

資源有效時,wake_up()對整個等待隊列的喚醒是經過對每一個等待隊列上的進程調用wake_up_process()實現的。

wake_up_interruptible():TASK_INTERRUPTIBLE-> TASK_RUNNING
   將wait_queue中的全部狀態爲 TASK_INTERRUPTIBLE的進程狀態都置爲TASK_RUNNING,並將它們都放到running queue中去。
   這個函數一般在send_sig(發出信號)後調用,以使信號發出後能及時獲得響應,或者當空閒下來時,但願檢查一下是否有收到有效信號的能運行的進程時,也能夠調用這個函數,如:
在進程退出前調用notify_parent(),給父進程send_sig()後,將調用wake_up_interruptible (),使信號可以獲得及時的響應。
usr/src/linux/KERNEL/EXIT.C 中定義了
void notify_parent(struct task_struct * tsk, int signal)
{
        send_sig(signal, tsk->p_pptr, 1);
        wake_up_interruptible(&tsk->p_pptr->wait_chldexit);
}
   當某一進程要結束時,它能夠經過調用notify_parent(current, current->exit_signal)通知父進程以喚醒睡眠在wait_chldexit上的父進程


2. Semaphores(信號燈)

  信號量用於生成鎖機制,避免發生數據不一致。
  信號量最簡單的形式就是內存中一個位置,它的取值能夠由多個進程檢驗和設置。檢驗和設置的操做,至少對於關聯的每個進程來說,是不可中斷或者說有原子性:只要啓動就不能停止。檢驗和設置操做的結果是信號燈當前值和設置值的和,能夠是正或者負。根據測試和設置操做的結果,一個進程可能必須睡眠直到信號燈的值被另外一個進程改變。信號燈能夠用於實現臨界區域(critical regions),就是重要的代碼區,同一時刻只能有一個進程運行。
   對信號燈的操做是經過如下兩組基本函數實現的:
1.void __up(struct semaphore *sem) :TASK_UNINTERRUPTIBLE-> TASK_RUNNING;
                                  TASK_INTERRUPTIBLE-> TASK_RUNNING
    int __do_down(struct semaphore * sem, int task_state)由如下兩個函數調用,分別轉換到不一樣的等待狀態:
(1)int __down_interruptible (struct semaphore * sem):
   TASK_RUNNING ->TASK_INTERRUPTIBLE;
(2)void __down(struct semaphore * sem):
   TASK_RUNNING ->TASK_UNINTERRUPTIBLE;
2. extern inline void up(struct semaphore * sem)
   extern inline void down(struct semaphore * sem);
   extern inline int down_interruptible(struct semaphore * sem);

   Linux信號量是經過兩路counter變量實現的:當進程因爲申請不到臨界區資源而睡眠時,會將semaphore結構中的」count」變量值原子地遞減1,進程睡眠等待臨界區資源的釋放;而當up()函數喚醒睡眠等待進程時,若是」count」變量值小於0,會將semaphore結構中的」 waking」變量值原子地遞增1,喚醒睡眠進程。雖然全部等待進程都被喚醒。但只有首先獲得」 waking」的進程才能獲得信號量,繼續運行下去,其餘進程仍然回到最初的等待狀態。

Linux定義信號燈結構是:
struct semaphore {
        atomic_t count;
        int waking;
        struct wait_queue * wait;
};
   信號燈的值初始化爲一個宏定義的結構MUTEX的值{count=1,waking=0,wait=NULL}。

void __up(struct semaphore *sem):
佔有臨界區資源的進程,調用__up()釋放資源。在__up()函數中,調用wake_one_more ()函數,原子地讀sem->count, 若是sem->count <=0,則sem->waking ++,並喚醒全部等待在該sem-->wait上的進程。
void __up(struct semaphore *sem)
{
        wake_one_more(sem);
        wake_up(&sem->wait);
}


int __do_down(struct semaphore * sem, int task_state):
申請臨界區資源的進程會經過調用__do_down()來競爭資源。在__do_down()函數中,調用waking_non_zero(struct semaphore *sem)或waking_non_zero_interruptible(struct semaphore *sem)搶佔臨界區資源,若是搶佔到,則將當前進程置爲TASK_RUNNING,不然將當前進程的狀態置爲task_state,並處於循環等待狀態。
進程經過waking_non_zero()來競爭臨界區資源,在該函數中判斷sem-->waking的值,若是sem-->waking 大於0,sem->waking -- 並返回1,不然返回0。
int __do_down(struct semaphore * sem, int task_state)
{
        struct task_struct *tsk = current;
        struct wait_queue wait = { tsk, NULL };
        int                  ret = 0 ;

        tsk->state = task_state;
        add_wait_queue(&sem->wait, &wait);  /*將進程加入到等待隊列*/

        for (;;)
        {
                if (waking_non_zero(sem))        /* 是否已經被喚醒  */
                    break ;                            /* 是的,跳出循環 */

                if (   task_state == TASK_INTERRUPTIBLE
                    && (tsk->signal & ~tsk->blocked)
        /* 若是進程狀態爲TASK_INTERRUPTIBLE,且收到信號量,並未被屏蔽*/
                   )
                {
                    ret = -EINTR ;                     /* 中斷 */
                    atomic_inc(&sem->count) ;        /* 放棄down操做,原子遞增信號量的count值 */
                    break ;
                }

                schedule();                    /* 從新調度 */
                tsk->state = task_state;      /*未能競爭到信號量的進程從新置成執行down操
                                        做前的狀態*/
        }

        tsk->state = TASK_RUNNING;        /*競爭到信號量的進程置爲TASK_RUNNING狀態*/
        remove_wait_queue(&sem->wait, &wait);/*將進程從等待隊列中刪除*/
        return(ret) ;

} /* __do_down */

其中_do__down()又分別由__down()和__do_down()調用,進程轉換到不一樣狀態。
void __down(struct semaphore * sem):    TASK_RUNNING ->TASK_UNINTERRUPTIBLE;
void __down(struct semaphore * sem)
{
        __do_down(sem,TASK_UNINTERRUPTIBLE) ;
}

int __down_interruptible (struct semaphore * sem): TASK_RUNNING ->TASK_INTERRUPTIBLE;
int __down_interruptible(struct semaphore * sem)
{
        return(__do_down(sem,TASK_INTERRUPTIBLE)) ;
}

在Linux中定義了兩種不一樣的信號燈:
(1)定義在某個數據結構上:
   在linux系統中有不少數據結構中定義了這樣的信號燈,來控制對這個數據結構的資源訪問,好比不容許對某個內存單元進行多進程訪問,就經過定義在該內存單元上的某個信號燈mmap_sem進行__up()、_down()、up()、down()操做。
如:
struct mm_struct中有mmap_sem信號燈;
struct inode中有i_sem、i_atomic_write信號燈;
struct nlm_file中有f_sema信號燈;
struct nlm_host中有h_sema信號燈;
struct superblock中有s_vfs_rename_sem信號燈;
struct vfsmount中有mnt_dquot.semaphore信號燈;
struct task_struct中有vfork_sem信號燈;//注:這個信號燈在2.0.36版本是沒有的,新版本2.2.8中才有的,用於vfork()。
struct unix_opt中有readsem信號燈;
struct smb_sb_info中有sem信號燈;
申請這些數據結構中的臨界區資源,就要進行相應的信號燈操做。

(2)定義在全局的單獨信號燈數據:
   還有一些單獨的全局信號燈,它們並不屬於某一個數據結構,而是系統定義的全局靜態的信號燈,可能有多個進程對這種不屬於某個特定數據結構的全局臨界資源的申請,則系統經過這些全局信號燈來分配資源。
如:
nlm_file_sema;
nlmsvc_sema;
lockd_start;
read_sem;
nlm_host_sema;
read_semaphore;
uts_sem
mount_sem;
cache_chain_sem;
rpciod_sema;
rpciod_running;
mfw_sema;
firewall_sem;

   咱們來分析一個例子說明信號燈的操做。例如對文件的寫操做,咱們假設有許多協做的進程對一個單一的數據文件進行寫操做。咱們但願對文件的訪問必須嚴格地協調。所以這裏就利用了inode結構上定義的信號燈inode->i_sem。
在 /usr/src/linux/mm/filemap.c中:
static int filemap_write_page(struct vm_area_struct * vma,
        unsigned long offset,
        unsigned long page)
{
        int result;
        struct file file;
    struct inode * inode;
        struct buffer_head * bh;

        ……………

        down(&inode->i_sem);
        result = do_write_page(inode, &file, (const char *) page, offset);
        up(&inode->i_sem);
        return result;
}

   在該文件寫操做的代碼中,加入兩個信號燈操做,第一個down(&inode->i_sem)檢查並把信號燈的值減少,第二個up(&inode->i_sem)檢查並增長它。訪問文件的第一個進程試圖減少信號燈的數值,若是成功,信號燈的count取值成爲0。這個進程如今能夠繼續運行並使用數據文件。可是,若是另外一個進程須要使用這個文件,如今它試圖減小信號燈的count數值,它會失敗由於結果會是-1。這個進程會被掛起直到第一個進程處理完數據文件。當第一個進程處理完數據文件,它會增長信號燈的waking數值成爲1。如今等待進程會被喚醒,此次它減少信號燈的嘗試會成功。

   每個獨立的信號燈操做可能都須要維護一個調整動做。Linux至少爲每個進程的每個信號燈數組都維護一個sem_undo的數據結構。若是請求的進程沒有,就在須要的時候爲它建立一個。這個新的sem_undo數據結構同時在進程的task_struct數據結構和信號燈隊列的semid_ds數據結構的隊列中排隊。對信號燈隊列中的信號燈執行操做的時候,和這個操做值相抵消的值加到這個進程的sem_undo數據結構的調整隊列這個信號燈的條目上。因此,若是操做值爲2,那麼這個就在這個信號燈的調整條目上增長-2。

   當進程被刪除,好比退出的時候,Linux遍歷它的sem_undo數據結構組,並實施對於信號燈數組的調整。若是刪除信號燈,它的sem_undo數據結構仍舊停留在進程的task_struct隊列中,可是相應的信號燈數組標識符標記爲無效。這種狀況下,清除信號燈的代碼只是簡單地廢棄這個sem_undo數據結構。


3.鎖機制
   lock_…();
   unlock_…();
   wait_on_…():TASK_RUNNING ->TASK_UNINTERRUPTIBLE;
   進程在RUNNING,WAITING狀態間轉換時,鎖機制也是Linux中解決進程之間共享資源的一個方法。鎖就是在資源的結構定義中加入一個鎖成員,或爲一個標誌位,它的取值能夠由多個進程檢驗和設置。鎖能夠用於實現對資源的共享競爭。具體來講當一個進程佔用一個資源時,先對其上鎖,而後再進行相關的操做,若是這時別的進程也要用這個資源,則必須等待這個鎖被解開後,才能夠進行下去。
   可是,鎖僅在某些數據結構和資源申請中才會用到,進程在申請某種特定資源時,會調用相應的__wait_on_… 函數來判斷是否該資源已經被上鎖,若是未上鎖或已被解鎖,則分配資源給進程,不然進程加入到等待隊列中去。這種類型的申請有:__wait_on_dquot、__wait_on_buffer、__wait_on_inode、__wait_on_page、__wait_on_super等。
   值得注意的是,若是申請不到這種資源,進程的狀態都是轉變成TASK_UNINTERRUPTIBLE。
   定義鎖的方式有兩種:
專門的某項數據結構:
如:Superblock的數據結構中專門定義了鎖數據項:s_lock;
置數據結構中某一項的某個標誌位爲鎖標誌:
如:
struct inode中定義了i_state的數據項,經過判斷i_state 是否置了 I_LOCK,來判斷該inode節點是否上鎖。(2.2.8版本中定義)//注意:在2.2.0.34版本中是採用了專門的數據項i_lock來進行鎖操做的。
struct buffer_head 中定義了b_state的數據項,經過判斷b_state是否置了 BH_Lock位,來判斷該緩衝區頭是否上鎖。
struct dquot中定義了dq_flags的數據項,經過判斷dq_flags是否置了DQ_LOCKED位,來判斷該dquot是否上鎖。
struct page中定義了flags的數據項,經過判斷flags是否置了PG_locked 位,來判斷該頁是否上鎖。//注:程序中通常採用PageLocked(page)函數來判斷是否上鎖。

   咱們以buffer_head的加鎖和解鎖操做爲例來解釋一下經過鎖機制進行的狀態轉換,在這裏要申請buffer_head 資源,先要申請到鎖,buffer_head的加鎖和解鎖就是經過置位和復位bh->b_state來實現的:
//申請資源時將該緩衝區上鎖,置鎖位,若是申請不到,睡眠在等待隊列上,等待該鎖的釋放。
extern inline void lock_buffer(struct buffer_head * bh)
{
        while (set_bit(BH_Lock, &bh->b_state))
                __wait_on_buffer(bh);
}

//資源釋放時,清該緩衝區鎖位,並喚醒等待隊列上的進程,參與競爭資源。
void unlock_buffer(struct buffer_head * bh)
{
        ......

        clear_bit(BH_Lock, &bh->b_state);
        wake_up(&bh->b_wait);
        ......
}

//檢驗該鎖位是否已經置位
static inline int buffer_locked(struct buffer_head * bh)
{
        return test_bit(BH_Lock, &bh->b_state);
}

//在 /USR/SRC/LINUX/FS/BUFFER.C中定義了__wait_on_buffer(stuct buffer_head * bh);該函數判斷該buffer_head是否已經被上了鎖,若是是,則不能獲得資源,將進程置成TASK_UNINTERRUPTIBLE,加入bh-->b_wait隊列中,調用schedule()轉去調用其餘的進程,不然,分配給資源,進程進入TASK_running狀態。
void __wait_on_buffer(struct buffer_head * bh)
{
        struct wait_queue wait = { current, NULL };

        bh->b_count++;
        add_wait_queue(&bh->b_wait, &wait);/*進程加入到等待鎖的隊列*/
repeat:
        run_task_queue(&tq_disk);
        current->state = TASK_UNINTERRUPTIBLE;/*進程狀態置爲TASK_UNINTERRUPTIBLE*/
        if (buffer_locked(bh)) {
                schedule();    /*若是申請不到鎖,從新調度CPU*/
                goto repeat;
        }
        remove_wait_queue(&bh->b_wait, &wait);/*進程從等待隊列中刪除*/
        bh->b_count--;
        current->state = TASK_RUNNING; /*進程狀態置爲TASK_ RUNNING*/
}


4. 管道(流)
   管道作爲系統的特殊設備文件,能夠是內存方式的,也能夠是外存方式的。管道的傳輸通常是單向的,即一個管道一貫,若兩個進程要作雙向傳輸則須要2個管道.管道生成時即有兩端,一端爲讀,一端爲寫,兩個進程要協調好,一個進程從讀方讀,另外一個進程向寫方寫。管道的讀寫使用流設備的讀寫函數,即:read(),write.管道的傳輸方式爲FIFO,流方式的.不象消息隊列能夠按類型讀取.管道分爲有名管道和無名管道:
1. 有名管道
    通常爲系統特殊文件方式,使用的進程之間不必定要有父子關係或兄弟關係.
2. 無名管道
    通常爲內存方式,使用的進程之間必定要有父子關係或兄弟關係.

  Linux shell容許重定向。例如:

  $ ls | pr | lpr

  把列出目錄文件的命令ls的輸出經過管道接到pr命令的標準輸入上進行分頁。最後,pr命令的標準輸出經過管道鏈接到lpr命令的標準輸入上,在缺省打印機上打印出結果。管道是單向的字節流,把一個進程的標準輸出和另外一個進程的標準輸入鏈接在一塊兒。沒有一個進程意識到這種重定向,和它日常同樣工做。是shell創建了進程之間的臨時管道。在Linux中,使用指向同一個臨時VFS INODE節點(自己指向內存中的一個物理頁)的兩個file數據結構來實現管道。當寫進程向管道中寫的時候,字節拷貝到了共享的數據頁,當從管道中讀的時候,字節從共享頁中拷貝出來。Linux必須同步對於管道的訪問。必須保證管道的寫和讀步調一致,它使用鎖、等待隊列和信號。
        運用管道方式進行通信的進程,因爲都是調用sleep_on_interruptible,所以都是睡眠在TASK_INTERRUPTIBLE狀態的。

管道結構的定義在include/linux/pipe_fs_i.h中,
struct pipe_inode_info {
        struct wait_queue * wait;
        char * base;
        unsigned int start;
        unsigned int len;
        unsigned int lock; //用到了鎖
        unsigned int rd_openers;
        unsigned int wr_openers;
        unsigned int readers;
        unsigned int writers;
};

對管道的操做主要有讀和寫兩種:
1.向一個管道寫pipe_write():
   在/fs/pipe.c中定義了static int pipe_write(struct inode * inode, struct file * filp, const char * buf, int count);
實現機制:當寫進程向管道寫的時候,它使用標準的write庫函數。這些庫函數傳遞的文件描述符是進程的file數據結構組中的索引,每個都表示一個打開的文件,在這種狀況下,是打開的管道。Linux系統調用使用描述這個管道的file數據結構指向的write例程。這個write例程使用表示管道的VFS INODE節點存放的信息,來管理寫的請求。若是有足夠的空間把全部的字節都寫導管到中,只要管道沒有被讀進程鎖定,Linux爲寫進程上鎖,並把字節從進程的地址空間拷貝到共享的數據頁。若是管道被讀進程鎖定或者空間不夠,當前進程睡眠,並放在管道INODE節點的等待隊列中,並調用調度程序,運行另一個進程。它是能夠中斷的,因此它能夠接收信號。當管道中有了足夠的空間寫數據或者鎖定解除,寫進程就會被讀進程喚醒。當數據寫完以後,管道的VFS INODE 節點鎖定解除,管道INODE節點的等待隊列中的全部讀進程都會被喚醒。

2.從一個管道讀Pipe_read():
   在/fs/pipe.c中定義了static int pipe_read(struct inode * inode, struct file * filp, char * buf, int count);
實現機制:從管道中讀取數據和寫數據很是類似。進程容許進行非阻塞的讀(依賴於它們打開文件或者管道的模式),這時,若是沒有數據可讀或者管道被鎖定,會返回一個錯誤。這意味着進程會繼續運行。另外一種方式是在管道的INODE節點的等待隊列中等待,直到寫進程完成。若是管道的進程都完成了操做,管道的INODE節點和相應的共享數據頁被廢棄。

 

進程在TASK_RUNNING和TASK_STOPPED間的轉換:
1.進程從TASK_RUNNING->TASK_STOPPED的轉換:
TASK_STOPPED狀態是一種暫停狀態,和TASK_STOPPED狀態配合工做的標誌爲PF_PTRACED和PF_TRACESYS,分別表示被跟蹤和正在跟蹤系統調用,一個是被動的,一個是主動的。
進程可經過兩種途徑進入TASK_STOPPED狀態:
1).受其它進程的syscall_trace()系統調用的控制而暫時將CPU交給控制進程。
在調用syscall_trace()以前必須先調用sys_ptrace()(簡稱ptrace()),進行跟蹤系統調用以前的準備工做。只有調用sys_ptrace()後,current的PF_PTRACED和PF_TRACESYS標誌都已置位,跟蹤和被跟蹤的關係都已明確,再調用syscall_trace()才能真正使進程轉入STOPPED狀態。
        syscall_trace()實現步驟:
(1)檢驗是否調用過ptrace()作過準備,沒有則返回;
        (2)置狀態STOPPED ,通知父進程,子進程狀態已變;
        (3)進行CPU從新調度,將current進程從runqueue刪除。
(4)若是exit_code非空,將它的值做爲接收到的信號放到signal中。如果SIGTRAP
     則current進程將一直處於stopped,直到收到其它信號。

sys_ptrace()實現步驟:
(1)若是request == PTRACE_TRACEME,則有進程要求跟蹤current進程:
                若current進程已被其它進程跟蹤返回;
    不然置current進程已被進程跟蹤的標記;
(2)若是current進程想跟蹤其它進程:
            a.不能跟蹤init進程;
                b.找pid對應的進程child,找不到返回出錯;
                c.若是request爲PTRACE_ATTACH
    若是current進程試圖ATTACH本身,出錯;
                若是試圖attach的進程child已被attach,出錯;
                不然        child->flags |= PF_PTRACED;作上標記,child已被attach;若是child
    不是current的子進程,將它變成current的子進程;而且發SIGSTOP信號,暫
    停它。
(3)進行其餘合法性檢查;
    (4)判斷request,執行相應操做:
                case PTRACE_SYSCALL:繼續執行,在下一個系統調用返回處停下。
    case PTRACE_CONT:發信號後從新開始運行。
                        若是request == PTRACE_SYSCALL,置child標誌位PF_TRACESYS;
                        不然        清child標誌位PF_TRACESYS,以防止從新運行後因歷史緣由在下一個
        系統調用返回處停下;
                        喚醒child進程。
          case PTRACE_KILL: 想要結束child進程,喚醒child進程,並在退出信息
                      exit_code中存放SIGKILL信號。
                case PTRACE_SINGLESTEP:  進行單步運行環境設置。
                case PTRACE_DETACH: 恢復child進程的自由。清跟蹤標誌,並喚醒child進程                                        恢復child進程的原始親屬關係。

 

 

2).收到要求它暫停的信號。
另外一種進入STOPPED狀態的方法是信號,SIGSTOP信號使自由的current進程,打上PF_PTRACED標記,並將它轉入STOPPED狀態。do_signal在檢查進程收到的信號時,若發現current進程已打上PF_PTRACED標記,則除收到的SIGKILL信號的狀況外,current進程都將立刻進入STOPPED狀態。
do_signal()實現步驟:
(1)current進程已打上PF_PTRACED標記,則除收到的SIGKILL信號的狀況外,進程都將進入TASK_STOPPED狀態,通知父進程,並從新調度;
        (2)若是收到信號SIGSTOP:若是當前進程標誌位不是PF_PTRACED,則置當前進程狀態爲TASK_STOPPED; 通知父進程,並從新調度;

2.進程從TASK_STOPPED->TASK_RUNNING的轉換:
從TASK_STOPPED狀態轉到TASK_RUNNING狀態經過「信號喚醒」。當有SIGKILL或SIGCONT信號發給TASK_STOPPED狀態下的進程時,進程將被wake_up_process()喚醒。
int send_sig(unsigned long sig,struct task_struct * p,int priv)
{
        ………;
        save_flags(flags); cli();   /*保存標誌,關中斷*/
        if ((sig == SIGKILL) || (sig == SIGCONT)) {
                if (p->state == TASK_STOPPED)
                        wake_up_process(p);     /*若進程p的狀態是STOPPED,而且所發送的信號是SIGKILL和SIGCONT,將p狀態賦成RUNNING,並掛到run-queue*/
p->exit_code = 0;      /*退出信息沒有*/
                p->signal &= ~( (1<<(SIGSTOP-1)) | (1<<(SIGTSTP-1)) |
                                (1<<(SIGTTIN-1)) | (1<<(SIGTTOU-1)) );         /*處理過信號後,將p的可能曾接受到的SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU信號清掉*/
        }
        if (sig == SIGSTOP || sig == SIGTSTP || sig == SIGTTIN || sig == SIGTTOU)
                p->signal &= ~(1<<(SIGCONT-1));     /*若信號爲SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU中的任一種,將p可能曾接受到的SIGCONT信號清掉*/
        restore_flags(flags);          /*恢復CPU標誌同時打開中斷*/
        generate(sig,p);     /*登記不能當即被處理的信號。*/
        return 0;
}

 

 

進程的終止:從TASK_RUNNING->TASK_ZOMBIE的轉換
進程終止由可終止進程的系統調用經過調用do_exit()實現,do_exit()終止current進程,首先爲current進程作上PF_EXITING的標記,釋放current進程的存儲管理信息、文件系統、文件信息、信號響應函數指針數組,將狀態置成TASK_ZOMBIE,通知current的父進程,最後進行從新調度。do_exit()帶一個參數code,用於傳遞終止進程的緣由。

do_exit(long code)流程:
    (1)若是進程在中斷服務程序中調用do_exit(),則打印提示信息
    (2)記錄進程的記賬信息
    (3)進程標誌置爲PF_EXITING
(4)釋放定時器鏈表
        (5)釋放臨界區數據
        (6)將消息隊列中和current進程有關項刪除
        (7)釋放進程的存儲管理信息
        (8)釋放進程已打開文件的信息
        (9)釋放進程的文件系統
        (10)釋放進程的信號響應函數指針數組等管理信息
          (11)釋放進程的LDT
        (12)進程狀態置爲TASK_ZOMBIE
        (13)置上退出信息,通知全部進程親戚,它要退出了#
        (14)exec_domain結構共享計數減1, binfmt結構共享計數減1
(15)從新調度,將current進程從run-queue中刪除,交出CPU

exit_notify ()函數向全部和current進程有關的進程發相應的消息,以便它們開展工做,exit_notify ()還判斷cueernt進程所在組是否會因current進程的退出而懸空,若是懸空而且組內有stopped狀態的進程則發信號;同時進行一系列的指針調整,調整因current進程的死亡引發的進程關係轉變。
exit_notify ()流程:
將全部原始進程爲current的進程變成init進程的孫子。
若是父進程和current進程不在同一組,但在同一session內而且current進程組內全部進程的父進程和它在同一組,也就是說,current進程所在組會因current的退出而懸掛,同時current進程所在組內有stopped進程,就向整個組發SIGHUP和SIGCONT信號。
通知父進程進程死了。
調整全部current進程的子進程的父進程指針,將它們掛到它們的原始進程下,
     將以往的跟蹤被跟蹤歷史清除,調整它和新的兄弟的關係;檢查每個current
     進程的子進程所在的組是否會懸掛,若是子進程和current進程不在同一組,並
     且這個組已懸掛,組內有stopped的進程,就向組員發SIGHUP 和 SIGCONT信號。        (5)若是current進程是session的主管, 就和它所控制的tty脫離,向current
     進程顯示終端所在的組發SIGHUP 和 SIGCONT信號。


進程直接或間接地調用do_exit() 後,進程進入ZOMBIE狀態,還有一塊PCB未釋放。PCB的釋放必須由它的父進程執行,當父進程調用sys_wait4()時釋放進入ZOMBIE狀態的子進程的PCB。

具體調用do_exit()的函數有如下狀況:
具體對應的系統調用出錯,不得不終止進程,如:
do_page_fault():這個系統調用處理頁面錯誤,它找到頁面地址,出錯緣由,並將它轉入相應的處理函數。當發生越界(out of memory)等bad page的致命錯誤。

sys_sigreturn():通常狀況下sys_sigreturn()將sigcontext的內容保存到堆棧中,保存過程當中當發現段寄存器越界了,這個致命錯誤就將致使進程結束。

setup_frame():setup_frame()創建信號響應函數的運行的環境,保存當前寄存器,將相應寄存器賦上信號響應函數的信息。在具體設定以前首先進行存儲條件檢驗,不知足就不得不結束進程。

save_v86_state():save_v86_state()保存虛擬86模式下(virtual 86 mode)的信息,若是進程PCB中vm86的信息爲空的,沒法繼續進行操做,只能結束進程。

(2)其餘終止進程的狀況,經過調用如下函數實現終止:
sys_exit():是一個系統調用,實現終止調用它的當前進程。

sys_reboot():sys_reboot()只能被特權級用戶調用,用於從新啓動系統。

do_signal():do_signal()是處理信號的一個函數。檢查current進程每個接收到的signal,若是是結束進程的信號,結束進程進行相應處理。

die_if_kernel()。

[目錄]


線程


1        概述
1.1        線程的定義(Introduction)
Threads can best be described as 「lightweight processes」. The traditional UNIX-style notion of a process has been found to be inconvenient, if not inadequate for several applications in distributed systems development. The needs of these applications are best served by threads, which generalize the notion of a process so that it can be associated with multiple activities. The popularity of threads has resulted in their implementation on UNIX systems and thread libraries are now widely available to programmers for the development of concurrent applications.

1.2        Threads Implementation
Threads can be implemented in one of two ways:
1. User-level threads:
There is no kernel support for multi-threaded processes. Hence, the kernel only has a single-threaded process abstraction, but multi-threaded processes are implemented in a library of procedures linked with application programs. The kernel has no knowledge of lightweight processes (threads), and therefore cannot schedule them independently. A threads run-time library organizes the scheduling of threads. A thread would block the process and therefore all threads within it if it made a blocking system call, so the asynchronous I/O facilities of UNIX are used. The major disadvantage of this scheme is that threads within a process cannot take advantage of a multi-processor.
(上段譯文)User-level沒有核心支持的多線程的進程。所以,核心只有單線程進程概念,而多線程進程由與應用程序鏈接的過程庫實現。核心不知道線程的存在,也就不能獨立的調度這些線程了。一個線程運行庫組織線程的調度。若是一個線程調用了一個阻塞的系統調用,進程可能被阻塞,固然其中的全部線程也同時被阻塞,因此UNIX使用了異步I/O工具。這種機制的的最大缺點是不能發揮多處理器的優點。
The advantages include:
(系統消耗小)Certain thread operations are significantly less costly. For example, switching between threads belonging to the same process do not necessarily involve a system call, and hence save this over-head.
(能夠修改以適應特殊的應用)User-level thread implementations can be customized or changed to suit the particular application requirements. This is particularly useful for real-time multimedia processing etc. Also, it is possible to support many more user-level threads than can by default by a kernel.
2. Kernel-level threads:
This implementation allows threads within different processes to be scheduled according to a single scheme of relative prioritizing. This is suited for exploiting the concurrence of multiprocessors.
核心級線程如許不一樣進程裏的線程按照同一相對優先方法調度,這適合於發揮多處理器的併發優勢。
Most of the current thread library implementations available today implement user-level threads. There have been several research projects that have implemented some form of Kernel-level threads. Notable among these are the Mach distributed OS, which combines the advantages of user-level and kernel-level threads by allowing user-level code to provide scheduling hints to the kernel thread scheduler. By providing such a two-level scheduling scheme, the kernel retains control over the allocation of processor time, but also allows a process to take advantage of multiple processors.

1.3        Thread Libraries
The two most widely used thread libraries are POSIX and Solaris thread libraries. Both implementations are inter-operable, their functionality is similar, and can be used within the same application. However, only POSIX threads are guaranteed to be fully portable to other POSIX-compliant environments.
Similarities:
Most of the functions in both libraries, libpthread and libthread, have a counterpart in the other library. POSIX functions and Solaris functions, whose names have similar endings, usually have similar functionality, number of arguments, and use of arguments. All POSIX threads function names begin with the prefix pthread? where as the Solaris threads function names begin with the prefix thr?
Differences:
POSIX
is more portable
establishes characteristics for each thread according to configurable attribute objects
implements thread cancellation
enforces scheduling algorithms
allows for clean-up handlers for fork(2) calls
Solaris
threads can be suspended and continued
implements an optimized mutex, readers/writer locking
may increase the concurrency
implements daemon threads, for whose demise the process does not wait


1.4        Threads Standards
There are three different definitions for thread libraries competing for attention today: Win32, OS/2, and POSIX. The first two are proprietary and limited to their individual platforms (Win32 threads run only under NT and Win95, OS/2 threads on OS/2). The POSIX specification (IEEE 1003.1c, aka Pthreads) is intended for all computing platforms, and implementations are available or in the works for almost all major UNIX systems (including Linux), along with VMS.

POSIX Threads
The POSIX standard defines the API and behavior that all the Pthreads libraries must meet. It is part of the extended portion of POSIX, so it is not a requirement for meeting XPG4, but it is required for X/Open UNIX 98, and all major UNIX vendors have committed to meeting this standard. As of this writing, (7/97) almost all UNIX vendors have released a library.

Win32 and OS/2 Threads
Both the NT and OS/2 implementations contain some fairly radical differences
from the POSIX standard--to the degree that even porting from one or the other
to POSIX will prove moderately challenging. Microsoft has not announced any
plans to adopt POSIX. There are freeware POSIX libraries for Win32 (see
Commercial Products on page 249), and OS/2 also has an optional POSIX library.

DCE Threads
Before POSIX completed work on the standard, it produced a number of drafts which it published for comment. Draft 4 was used as the basis for the threads library in DCE. It is similar to the final spec, but it does contain a number of significant differences. Presumably, no one is writing any new DCE code.

Solaris Threads
Also known as UI threads, this is the library, which SunSoft used in developing Solaris 2 before the POSIX, committee completed their work. It will be available on Solaris 2 for the foreseeable future, although we expect most applications writers will opt for Pthreads. The vast majority of the two libraries are virtually identical.
1.5        Linux線程的思想及特色
1.5.1        LinuxThreads
http://pauillac.inria.fr/~xleroy/linuxthreads
Xavier Leroy at INRIA (Paris, France), with input from Pavel Krauz, Richard Henderson and others, has developed a Pthreads library that implements the One-to-One model, allowing it to take advantage of multiple processors. It is based on the new Linux system call, clone()2 . It runs on Linux 2.0 and up, on Intel, Alpha, SPARC, m68k, and MIPS machines. One limitation is its non-standard implementation of signal handling.
1.5.2        Implementation model for LinuxThreads
LinuxThreads follows the so-called "one-to-one" model: each thread is actually a separate process in the kernel. The kernel scheduler takes care of scheduling the threads, just like it schedules regular processes. The threads are created with the Linux clone() system call, which is a generalization of fork() allowing the new process to share the memory space, file descriptors, and signal handlers of the parent.
LinuxThreads採用稱爲1-1模型:每一個線程實際上在覈心是一個個單獨的進程,核心的調度程序負責線程的調度,就象調度普通進程。線程是用系統調用clone()建立的,clone()系統調用是fork()的廣泛形式,它容許新進程共享父進程的存儲空間、文件描述符和軟中斷處理程序。
Advantages of the "one-to-one" model include:

Minimal overhead on CPU-intensive multiprocessing (with about one thread per processor); 最小限度消耗的CPU級多處理技術(每一個CPU一個線程);
Minimal overhead on I/O operations; 最小限度消耗的I/O操做;
A simple and robust implementation (the kernel scheduler does most of the hard work for us);一種簡單和強壯的實現(核心調度程序爲咱們作了大部分艱難的工做)。

The main disadvantage is more expensive context switches on mutex and condition operations, which must go through the kernel. This is mitigated by the fact that context switches in the Linux kernel are pretty efficient.

1.5.3        Consider other implementation models

There are basically two other models. The "many-to-one" model relies on a user-level scheduler that context-switches between the threads entirely in user code; viewed from the kernel, there is only one process running. This model is completely out of the question for me, since it does not take advantage of multiprocessors, and require unholy magic to handle blocking I/O operations properly. There are several user-level thread libraries available for Linux, but I found all of them deficient in functionality, performance, and/or robustness.
還有另外兩種基本模型。多對一模型依賴於用戶級的調度程序,線程切換徹底由用戶程序完成;從核心角度看,只有一個進程正在運行。這種模型不是咱們所關心的,由於它沒法利用多處理器的優勢,並且要用不合理的方法處理I/O操做阻塞。
The "many-to-many" model combines both kernel-level and user-level scheduling: several kernel-level threads run concurrently, each executing a user-level scheduler that selects between user threads. Most commercial Unix systems (Solaris, Digital Unix and IRIX) implement POSIX threads this way. This model combines the advantages of both the "many-to-one" and the "one-to-one" model, and is attractive because it avoids the worst-case behaviors of both models -- especially on kernels where context switches are expensive, such as Digital Unix. Unfortunately, it is pretty complex to implement, and requires kernel supporting which Linux does not provide. Linus Torvalds and other Linux kernel developers have always been pushing the "one-to-one" model in the name of overall simplicity, and are doing a pretty good job of making kernel-level context switches between threads efficient. LinuxThreads is just following the general direction they set.
2        Linux核心對線程的支持
Linux核心對線程的支持主要是經過其系統調用,下文將進行系統的介紹。
2.1        系統調用clone()
如下是系統調用clone的代碼:
asmlinkage int sys_clone(struct pt_regs regs)
{
        unsigned long clone_flags;
        unsigned long newsp;

        clone_flags = regs.ebx;
        newsp = regs.ecx;
if (!newsp)
                newsp = regs.esp;
        return do_fork(clone_flags, newsp, &regs);
}

與系統調用clone功能類似的系統調用有fork,但fork事實上只是clone的功能的一部分,clone與fork的主要區別在於傳遞了幾個參數,而當中最重要的參數就是conle_flags,下表是系統定義的幾個clone_flags標誌:
標誌        Value        含義
CLONE_VM        0x00000100        置起此標誌在進程間共享VM
CLONE_FS        0x00000200        置起此標誌在進程間共享文件系統信息
CLONE_FILES        0x00000400        置起此標誌在進程間共享打開的文件
CLONE_SIGHAND        0x00000800        置起此標誌在進程間共享信號處理程序
若是置起以上標誌所作的處理分別是:
置起CLONE_VM標誌:
                mmget(current->mm);
                /*
                * Set up the LDT descriptor for the clone task.
                */
                copy_segments(nr, tsk, NULL);
                SET_PAGE_DIR(tsk, current->mm->pgd);
置起CLONE_ FS標誌:
                atomic_inc(&current->fs->count);
置起CLONE_ FILES標誌:
                atomic_inc(&oldf->count);
置起CLONE_ SIGHAND標誌:
                atomic_inc(&current->sig->count);
2.2        與線程調度相關的系統調用
如下是glibc-linuxthread用來進行調度的系統調度:
        .long SYMBOL_NAME(sys_sched_setparam)   /* 系統調用154 */
/*用來設置進程(或線程)的調度參數*/
        .long SYMBOL_NAME(sys_sched_getparam)
/*用來獲取進程(或線程)的調度參數*/
        .long SYMBOL_NAME(sys_sched_setscheduler)
/*用來設置進程(或線程)的調度參數*/
        .long SYMBOL_NAME(sys_sched_getscheduler)
/*用來獲取進程(或線程)的調度參數*/
        .long SYMBOL_NAME(sys_sched_yield)
/*用來強制核心從新調度進程(或線程)*/
        .long SYMBOL_NAME(sys_sched_get_priority_max)
/*用來設置進程(或線程)的調度參數*/
        .long SYMBOL_NAME(sys_sched_get_priority_min)
/*用來獲取進程(或線程)的調度參數*/
        .long SYMBOL_NAME(sys_sched_rr_get_interval) /* 系統調用161 */
/*用來獲取進程(或線程)的調度時間間隔*/

3        Linux線程的實現
3.1        LinuxThreads概述
如今的0.8版LinuxThreads,是迄今爲止在Linux下支持threads的最好的Runtime-library,而包含0.8版LinuxThreads的最好的Runtime-library是glibc- 2.1,下文所要分析的正是glibc-linuxthreads-2.1。
首先介紹一下0.8版LinuxThreads,它實現了一種BiCapitalized面向Linux的Posix 1003.1c"pthread"標準接口。LinuxThreads提供核心級線程即每一個線程是一個獨立的UNIX進程,經過調用新的系統調用與其它線程共享地址空間。線程由核心調度,就象UNIX進程調度同樣。使用它的要求是:LINUX 版本2.0 或以上(要求有新的clone() 系統調用和新的實時調度程序)。對於Intel平臺:要求有libc 5.2.18或後續版本,推薦使用5.2.18 或 5.4.12 及其後續版本;5.3.12和5.4.7有問題,也支持glibc 2,其實是支持它的一個特別合適的版本。到目前支持Intel, Alpha, Sparc, Motorola 68k, ARM and MIPS平臺,還支持多處理器
3.2        主要的數據結構及初始化
3.2.1        數據結構和部分數據初始化
/* Arguments passed to thread creation routine */
//傳遞給線程建立程序的參數
struct pthread_start_args {
  void * (*start_routine)(void *); /* function to run */
  void * arg;                   /* its argument */
  sigset_t mask;                /* initial signal mask for thread */
  int schedpolicy;              /* initial scheduling policy (if any) */
  struct sched_param schedparam; /* initial scheduling parameters (if any) */
};

/* The type of thread descriptors */
//線程描述符類型
typedef struct _pthread_descr_struct * pthread_descr;

struct _pthread_descr_struct {
  pthread_descr p_nextlive, p_prevlive;
                                /* Double chaining of active threads */
  pthread_descr p_nextwaiting;  /* Next element in the queue holding the thr */
  pthread_t p_tid;              /* Thread identifier */
  int p_pid;                    /* PID of Unix process */
  int p_priority;               /* Thread priority (== 0 if not realtime) */
  struct _pthread_fastlock * p_lock; /* Spinlock for synchronized accesses */
  int p_signal;                 /* last signal received */
  sigjmp_buf * p_signal_jmp;    /* where to siglongjmp on a signal or NULL */
  sigjmp_buf * p_cancel_jmp;    /* where to siglongjmp on a cancel or NULL */
  char p_terminated;            /* true if terminated e.g. by pthread_exit */
  char p_detached;              /* true if detached */
  char p_exited;                /* true if the assoc. process terminated */
  void * p_retval;              /* placeholder for return value */
  int p_retcode;                /* placeholder for return code */
  pthread_descr p_joining;      /* thread joining on that thread or NULL */
  struct _pthread_cleanup_buffer * p_cleanup; /* cleanup functions */
  char p_cancelstate;           /* cancellation state */
  char p_canceltype;            /* cancellation type (deferred/async) */
  char p_canceled;              /* cancellation request pending */
  int * p_errnop;               /* pointer to used errno variable */
  int p_errno;                  /* error returned by last system call */
  int * p_h_errnop;             /* pointer to used h_errno variable */
  int p_h_errno;                /* error returned by last netdb function */
  char * p_in_sighandler;       /* stack address of sighandler, or NULL */
  char p_sigwaiting;            /* true if a sigwait() is in progress */
  struct pthread_start_args p_start_args; /* arguments for thread creation */
  void ** p_specific[PTHREAD_KEY_1STLEVEL_SIZE]; /* thread-specific data */
  void * p_libc_specific[_LIBC_TSD_KEY_N]; /* thread-specific data for libc */
  int p_userstack;                /* nonzero if the user provided the stack */
  void *p_guardaddr;                /* address of guard area or NULL */
  size_t p_guardsize;                /* size of guard area */
  pthread_descr p_self;                /* Pointer to this structure */
  int p_nr;                     /* Index of descriptor in __pthread_handles */
};

/* The type of thread handles. */
線程句柄
typedef struct pthread_handle_struct * pthread_handle;

struct pthread_handle_struct {
  struct _pthread_fastlock h_lock; /* Fast lock for sychronized access */
  pthread_descr h_descr;        /* Thread descriptor or NULL if invalid */
  char * h_bottom;              /* Lowest address in the stack thread */
};

/* The type of messages sent to the thread manager thread */
//發送給線程管理線程的請求
struct pthread_request {
  pthread_descr req_thread;     /* Thread doing the request */
  enum {                        /* Request kind */
    REQ_CREATE, REQ_FREE, REQ_PROCESS_EXIT, REQ_MAIN_THREAD_EXIT,
    REQ_POST, REQ_DEBUG
  } req_kind;
  union {                       /* Arguments for request */
    struct {                    /* For REQ_CREATE: */
      const pthread_attr_t * attr; /* thread attributes */
      void * (*fn)(void *);     /*   start function */
      void * arg;               /*   argument to start function */
      sigset_t mask;            /*   signal mask */
    } create;
    struct {                    /* For REQ_FREE: */
      pthread_t thread_id;      /*   identifier of thread to free */
    } free;
    struct {                    /* For REQ_PROCESS_EXIT: */
      int code;                 /*   exit status */
    } exit;
    void * post;                /* For REQ_POST: the semaphore */
  } req_args;
};

/* One end of the pipe for sending requests to the thread manager. */
//向管理線程發送請求的管道的一端;初始化爲-1表示管理線程尚未建立
int __pthread_manager_request = -1;

/* Other end of the pipe for sending requests to the thread manager. */

int __pthread_manager_reader;

//線程的堆棧大小
#define STACK_SIZE  (2 * 1024 * 1024)
//線程的初始堆棧大小
#define INITIAL_STACK_SIZE  (4 * PAGE_SIZE)

/* Attributes for threads.  */
//線程的屬性
typedef struct
{
  int __detachstate;
  int __schedpolicy;
  struct __sched_param __schedparam;
  int __inheritsched;
  int __scope;
  size_t __guardsize;
  int __stackaddr_set;
  void *__stackaddr;
  size_t __stacksize;
} pthread_attr_t;

//每一個進程的最大線程數
#define PTHREAD_THREADS_MAX        1024

3.2.2        Main thread and manager thread initializing

 

/* Thread creation */

int __pthread_create_2_1(pthread_t *thread, const pthread_attr_t *attr,
                        void * (*start_routine)(void *), void *arg)
{
  pthread_descr self = thread_self();
  struct pthread_request request;
  if (__pthread_manager_request < 0) {    //檢查是否啓動線程機制
        //初始化管理線程,啓動線程機制
    if (__pthread_initialize_manager() < 0) return EAGAIN;
  }
  request.req_thread = self;
  request.req_kind = REQ_CREATE;
  request.req_args.create.attr = attr;
  request.req_args.create.fn = start_routine;
  request.req_args.create.arg = arg;
  sigprocmask(SIG_SETMASK, (const sigset_t *) NULL,
              &request.req_args.create.mask);
//向管理線程發送請求
  __libc_write(__pthread_manager_request, (char *) &request, sizeof(request));
  suspend(self);
  if (THREAD_GETMEM(self, p_retcode) == 0)
    *thread = (pthread_t) THREAD_GETMEM(self, p_retval);
  return THREAD_GETMEM(self, p_retcode);
}

int __pthread_initialize_manager(void)
{
  int manager_pipe[2];
  int pid;
  struct pthread_request request;

  /* If basic initialization not done yet (e.g. we're called from a constructor run before our constructor), do it now */
//初始化初始線程
  if (__pthread_initial_thread_bos == NULL) pthread_initialize();
  /* Setup stack for thread manager */創建管理線程堆棧
  __pthread_manager_thread_bos = malloc(THREAD_MANAGER_STACK_SIZE);
  if (__pthread_manager_thread_bos == NULL) return -1;
  __pthread_manager_thread_tos =
    __pthread_manager_thread_bos + THREAD_MANAGER_STACK_SIZE;
  /* Setup pipe to communicate with thread manager */
//創建與管理線程通訊的管道
  if (pipe(manager_pipe) == -1) {
    free(__pthread_manager_thread_bos);
    return -1;
  }
  /* Start the thread manager */啓動管理線程
  pid = __clone(__pthread_manager, (void **) __pthread_manager_thread_tos,
                CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND
                , (void *)(long)manager_pipe[0]);
  if (pid == -1) {
    free(__pthread_manager_thread_bos);
    __libc_close(manager_pipe[0]);
    __libc_close(manager_pipe[1]);
    return -1;
  }
  __pthread_manager_request = manager_pipe[1]; /* writing end */
  __pthread_manager_reader = manager_pipe[0]; /* reading end */
  __pthread_manager_thread.p_pid = pid;
  /* Make gdb aware of new thread manager */
  if (__pthread_threads_debug && __pthread_sig_debug > 0)
    {
      raise(__pthread_sig_debug);
      /* We suspend ourself and gdb will wake us up when it is
        ready to handle us. */
      suspend(thread_self());
    }
  /* Synchronize debugging of the thread manager */
  request.req_kind = REQ_DEBUG;
  __libc_write(__pthread_manager_request, (char *) &request, sizeof(request));
  return 0;
}
//初始化初始線程
static void pthread_initialize(void)
{
  struct sigaction sa;
  sigset_t mask;
  struct rlimit limit;
  int max_stack;

  /* If already done (e.g. by a constructor called earlier!), bail out */
  if (__pthread_initial_thread_bos != NULL) return;
#ifdef TEST_FOR_COMPARE_AND_SWAP
  /* Test if compare-and-swap is available */
  __pthread_has_cas = compare_and_swap_is_available();
#endif
  /* For the initial stack, reserve at least STACK_SIZE bytes of stack below the current stack address, and align that on a STACK_SIZE boundary. */
//當前堆棧下爲初始堆棧留出至少STACK_SIZE,並按STACK_SIZE對齊
  __pthread_initial_thread_bos =
    (char *)(((long)CURRENT_STACK_FRAME - 2 * STACK_SIZE) & ~(STACK_SIZE - 1));
  /* Play with the stack size limit to make sure that no stack ever grows
     beyond STACK_SIZE minus two pages (one page for the thread descriptor
     immediately beyond, and one page to act as a guard page). */
//調整堆棧大小限制使其不能增加超過STACK_SIZE減兩頁(一頁給線程
//描述符,一頁做爲保護頁)
  getrlimit(RLIMIT_STACK, &limit);
  max_stack = STACK_SIZE - 2 * __getpagesize();
  if (limit.rlim_cur > max_stack) {
    limit.rlim_cur = max_stack;
    setrlimit(RLIMIT_STACK, &limit);
  }
  /* Update the descriptor for the initial thread. */
  __pthread_initial_thread.p_pid = __getpid();
  /* If we have special thread_self processing, initialize that for the
     main thread now.  */
#ifdef INIT_THREAD_SELF
  INIT_THREAD_SELF(&__pthread_initial_thread, 0);
#endif
  /* The errno/h_errno variable of the main thread are the global ones.  */
  __pthread_initial_thread.p_errnop = &_errno;
  __pthread_initial_thread.p_h_errnop = &_h_errno;
#ifdef SIGRTMIN
  /* Allocate the signals used.  */分配使用的軟中斷號
  __pthread_sig_restart = __libc_allocate_rtsig (1);
  __pthread_sig_cancel = __libc_allocate_rtsig (1);
  __pthread_sig_debug = __libc_allocate_rtsig (1);
  if (__pthread_sig_restart < 0 ||
      __pthread_sig_cancel < 0 ||
      __pthread_sig_debug < 0)
    {
      /* The kernel does not support real-time signals.  Use as before
        the available signals in the fixed set.
         Debugging is not supported in this case. */
      __pthread_sig_restart = DEFAULT_SIG_RESTART;
      __pthread_sig_cancel = DEFAULT_SIG_CANCEL;
      __pthread_sig_debug = 0;
    }
#endif
  /* Setup signal handlers for the initial thread.
     Since signal handlers are shared between threads, these settings
     will be inherited by all other threads. */
//設置初始進程的信號處理程序
#ifndef __i386__
  sa.sa_handler = pthread_handle_sigrestart;
#else
  sa.sa_handler = (__sighandler_t) pthread_handle_sigrestart;
#endif
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  __sigaction(__pthread_sig_restart, &sa, NULL);
#ifndef __i386__
  sa.sa_handler = pthread_handle_sigcancel;
#else
  sa.sa_handler = (__sighandler_t) pthread_handle_sigcancel;
#endif
  sa.sa_flags = 0;
  __sigaction(__pthread_sig_cancel, &sa, NULL);
  if (__pthread_sig_debug > 0) {
    sa.sa_handler = pthread_handle_sigdebug;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    __sigaction(__pthread_sig_debug, &sa, NULL);
  }
  /* Initially, block __pthread_sig_restart. Will be unblocked on demand. */
  sigemptyset(&mask);
  sigaddset(&mask, __pthread_sig_restart);
  sigprocmask(SIG_BLOCK, &mask, NULL);
  /* Register an exit function to kill all other threads. */
  /* Do it early so that user-registered atexit functions are called
     before pthread_exit_process. */
  __on_exit(pthread_exit_process, NULL);
}

3.3        線程的建立

Manager thread 接到建立線程請求後調用下函數。
static int pthread_handle_create(pthread_t *thread, const pthread_attr_t *attr,
                                void * (*start_routine)(void *), void *arg,
                                sigset_t * mask, int father_pid)
{
  size_t sseg;
  int pid;
  pthread_descr new_thread;
  char * new_thread_bottom;
  pthread_t new_thread_id;
  char *guardaddr = NULL;
  size_t guardsize = 0;
  int pagesize = __getpagesize();

  /* First check whether we have to change the policy and if yes, whether
     we can  do this.  Normally this should be done by examining the
     return value of the __sched_setscheduler call in pthread_start_thread
     but this is hard to implement.  FIXME  */
//檢查是否須要調整調度策略,若是須要,是否可以作到
  if (attr != NULL && attr->__schedpolicy != SCHED_OTHER && geteuid () != 0)
    return EPERM;
  /* Find a free segment for the thread, and allocate a stack if needed */
//找出一個空段,若是須要再分配堆棧
  for (sseg = 2; ; sseg++)
    {
      if (sseg >= PTHREAD_THREADS_MAX)
        return EAGAIN;
      if (__pthread_handles[sseg].h_descr != NULL)
        continue;
      if (pthread_allocate_stack(attr, thread_segment(sseg), pagesize,
                                 &new_thread, &new_thread_bottom,
                                 &guardaddr, &guardsize) == 0)
        break;
    }
  __pthread_handles_num++;
  /* Allocate new thread identifier */分配新線程的標識符
  pthread_threads_counter += PTHREAD_THREADS_MAX;
  new_thread_id = sseg + pthread_threads_counter;
  /* Initialize the thread descriptor */初始化新線程描述符
  new_thread->p_nextwaiting = NULL;
  new_thread->p_tid = new_thread_id;
  new_thread->p_priority = 0;
  new_thread->p_lock = &(__pthread_handles[sseg].h_lock);
  new_thread->p_signal = 0;
  new_thread->p_signal_jmp = NULL;
  new_thread->p_cancel_jmp = NULL;
  new_thread->p_terminated = 0;
  new_thread->p_detached = attr == NULL ? 0 : attr->__detachstate;
  new_thread->p_exited = 0;
  new_thread->p_retval = NULL;
  new_thread->p_joining = NULL;
  new_thread->p_cleanup = NULL;
  new_thread->p_cancelstate = PTHREAD_CANCEL_ENABLE;
  new_thread->p_canceltype = PTHREAD_CANCEL_DEFERRED;
  new_thread->p_canceled = 0;
  new_thread->p_errnop = &new_thread->p_errno;
  new_thread->p_errno = 0;
  new_thread->p_h_errnop = &new_thread->p_h_errno;
  new_thread->p_h_errno = 0;
  new_thread->p_in_sighandler = NULL;
  new_thread->p_sigwaiting = 0;
  new_thread->p_guardaddr = guardaddr;
  new_thread->p_guardsize = guardsize;
  new_thread->p_userstack = attr != NULL && attr->__stackaddr_set;
  memset (new_thread->p_specific, '/0',
          PTHREAD_KEY_1STLEVEL_SIZE * sizeof (new_thread->p_specific[0]));
  new_thread->p_self = new_thread;
  new_thread->p_nr = sseg;
  /* Initialize the thread handle */
  __pthread_init_lock(&__pthread_handles[sseg].h_lock);
  __pthread_handles[sseg].h_descr = new_thread;
  __pthread_handles[sseg].h_bottom = new_thread_bottom;
  /* Determine scheduling parameters for the thread */
//肯定線程的調度參數
  new_thread->p_start_args.schedpolicy = -1;
  if (attr != NULL) {
    switch(attr->__inheritsched) {
    case PTHREAD_EXPLICIT_SCHED:
      new_thread->p_start_args.schedpolicy = attr->__schedpolicy;
      memcpy (&new_thread->p_start_args.schedparam, &attr->__schedparam,
              sizeof (struct sched_param));
      break;
    case PTHREAD_INHERIT_SCHED:
      /* schedpolicy doesn't need to be set, only get priority */
      __sched_getparam(father_pid, &new_thread->p_start_args.schedparam);
      break;
    }
    new_thread->p_priority =
      new_thread->p_start_args.schedparam.sched_priority;
  }
  /* Finish setting up arguments to pthread_start_thread */
//設置pthread_start_thread的參數
  new_thread->p_start_args.start_routine = start_routine;
  new_thread->p_start_args.arg = arg;
  new_thread->p_start_args.mask = *mask;
  /* Raise priority of thread manager if needed */根據須要調整管理線程的優先級
  __pthread_manager_adjust_prio(new_thread->p_priority);
  /* Do the cloning */建立新線程
  pid = __clone(pthread_start_thread, (void **) new_thread,
                CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
                __pthread_sig_cancel, new_thread);
  /* Check if cloning succeeded */
  if (pid == -1) {
    /* Free the stack if we allocated it */
    if (attr == NULL || !attr->__stackaddr_set)
      {
        munmap((caddr_t)((char *)(new_thread+1) - INITIAL_STACK_SIZE),
               INITIAL_STACK_SIZE);
        if (new_thread->p_guardsize != 0)
          munmap(new_thread->p_guardaddr, new_thread->p_guardsize);
      }
    __pthread_handles[sseg].h_descr = NULL;
    __pthread_handles[sseg].h_bottom = NULL;
    __pthread_handles_num--;
    return errno;
  }
  /* Insert new thread in doubly linked list of active threads */
//將新線程插入雙向鏈表
  new_thread->p_prevlive = __pthread_main_thread;
  new_thread->p_nextlive = __pthread_main_thread->p_nextlive;
  __pthread_main_thread->p_nextlive->p_prevlive = new_thread;
  __pthread_main_thread->p_nextlive = new_thread;
  /* Set pid field of the new thread, in case we get there before the
     child starts. */
  new_thread->p_pid = pid;
  /* We're all set */
  *thread = new_thread_id;
  return 0;
}


3.4        線程的堆棧分配和管理
STACK_SIZE    2*1024*1024
INITIAL_STACK_SIZE    4*PAGE_SIZE
THREAD_MANAGER_STACK_SIZE      2*PAGE_SIZE-32

static int pthread_allocate_stack(const pthread_attr_t *attr,
                                  pthread_descr default_new_thread,
                                  int pagesize,
                                  pthread_descr * out_new_thread,
                                  char ** out_new_thread_bottom,
                                  char ** out_guardaddr,
                                  size_t * out_guardsize)
{
  pthread_descr new_thread;
  char * new_thread_bottom;
  char * guardaddr;
  size_t stacksize, guardsize;

  if (attr != NULL && attr->__stackaddr_set)
    {
      /* The user provided a stack. */用戶提供堆棧
      new_thread =
        (pthread_descr) ((long)(attr->__stackaddr) & -sizeof(void *)) - 1;
      new_thread_bottom = (char *) attr->__stackaddr - attr->__stacksize;
      guardaddr = NULL;
      guardsize = 0;
      __pthread_nonstandard_stacks = 1;
    }
  else
    {
      /* Allocate space for stack and thread descriptor at default address */
//在缺省地址分配堆棧和描述符
      new_thread = default_new_thread;
      new_thread_bottom = (char *) new_thread - STACK_SIZE;
      if (mmap((caddr_t)((char *)(new_thread + 1) - INITIAL_STACK_SIZE), INITIAL_STACK_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_GROWSDOWN, -1, 0) == MAP_FAILED)
        /* Bad luck, this segment is already mapped. */
        return -1;
      /* We manage to get a stack.  Now see whether we need a guard
         and allocate it if necessary.  Notice that the default
         attributes (stack_size = STACK_SIZE - pagesize and
         guardsize = pagesize) do not need a guard page, since
         the RLIMIT_STACK soft limit prevents stacks from
         running into one another. */
//判斷是否須要保護頁,若是須要就分配
      if (attr == NULL ||
          attr->__guardsize == 0 ||
          (attr->__guardsize == pagesize &&
           attr->__stacksize == STACK_SIZE - pagesize))
        {
          /* We don't need a guard page. */
          guardaddr = NULL;
          guardsize = 0;
        }
      else
        {
          /* Put a bad page at the bottom of the stack */
          stacksize = roundup(attr->__stacksize, pagesize);
          if (stacksize >= STACK_SIZE - pagesize)
            stacksize = STACK_SIZE - pagesize;
          guardaddr = (void *)new_thread - stacksize;
          guardsize = attr->__guardsize;
          if (mmap ((caddr_t) guardaddr, guardsize, 0, MAP_FIXED, -1, 0)
              == MAP_FAILED)
            {
              /* We don't make this an error.  */
              guardaddr = NULL;
              guardsize = 0;
            }
        }
    }
  *out_new_thread = new_thread;
  *out_new_thread_bottom = new_thread_bottom;
  *out_guardaddr = guardaddr;
  *out_guardsize = guardsize;
  return 0;
}


3.5        線程的調度
Common threads 的調度和普通進程並沒有大的區別,建立者能夠本身設定線程的優先級。可是Manager thread則須要實時響應各進程提出的請求,因此Manager thread被設置成高於其它線程的優先級,方法是在建立每一個新線程時調整Manager thread的優先級。

/* Adjust priority of thread manager so that it always run at a priority
   higher than all threads */

void __pthread_manager_adjust_prio(int thread_prio)
{
  struct sched_param param;

  if (thread_prio <= __pthread_manager_thread.p_priority) return;
  param.sched_priority =
    thread_prio < __sched_get_priority_max(SCHED_FIFO)
    ? thread_prio + 1 : thread_prio;
  __sched_setscheduler(__pthread_manager_thread.p_pid, SCHED_FIFO, &param);
  __pthread_manager_thread.p_priority = thread_prio;
}

 

[目錄]


進程描述符


標題   新兵筆記--ULK(C3) Process Descriptor
做者 Big John (stranger )
時間 05/19/01 06:01 PM

Process Descriptor

description:
進程描述符:也就是結構體task_struct,它有不少域,包含了一個進程的全部信息,主要有它的屬性、當前的狀態、它所佔有的資料,還有一些指針用於把按不一樣的需求把它鏈進不一樣的鏈表中。
進程描述符與進程的kernel棧:每一個進程都有個本身的kernel棧,當它進入kernel態時(好比進行系統調用),kernel會把棧指針指向當前進程的kernel棧。在2.2中,進程的描述符和kernel棧是捆在一塊兒的,
union task_union {
struct task_struct task;
unsigned long stack[2048];
};
kernel每次分配一個進程描述符總會按task_union的大小(即8k)"順手"分配一個kernel棧,這樣作一個最重要的目的就是爲了能方便的獲得當前運行進程的描述符,當系統須要時,老是使用current來獲得當前運行的進程,在2.0中current多是個全局變量或全局數組(當多CPU時),這樣一方面是不方便使用,另外一方面,在多CPU中還得去找當前CPU才能肯定當前的current(我當初看過,但忘了它是怎麼找的了)。如今使用了這個結構,kernel隨時能夠經過棧底指針減8K就可獲得描述符的地址。你們能夠看到如今的current實際是一個函數get_current,它所作的是就是把esp減去8K,而後返回這個地址。
進程描述符的分配與釋放:由這兩個函數完成,alloc_task_struct和free_task_struct。這兩個函數沒什麼花頭,仍是因爲分配物理頁幀的代碼過大,這裏也有一個緩存static struct task_struct * task_struct_stack[EXTRA_TASK_STRUCT],它能緩存16項,分配和釋放都儘可能在這進行,除非它已經滿了或空了,纔去與分頁系統打交道。
進程數組:struct task_struct *task[NR_TASKS];它是一個指針數組,其中NR_TASKS在i386下應該4090,實際可同時運行的進程應該是4092個,由於還得加上Process 0和Procces 1,這兩個進程是不在進程數組中的。
當建立一個進程時,kernel會申請一片內存並把它加入task數組中。如何加入呢?出於效率考慮,task數組並不象咱們平時處理那樣把沒有用的項置空,當要加入時順序的去找數組中爲空的項。它使用了相似於第二章中頁表緩存鏈表的管理方法,tarray_freelist是這個鏈表的表頭,具體操做以下:
初始化:
struct task_struct **tarray_freelist = NULL;
void __init sched_init(void)
{
。。。
int nr = NR_TASKS;
while(--nr > 0)
add_free_taskslot(&task[nr]); // 把task數組的每一項加到空閒鏈表中。
。。。
}
函數add_free_taskslot:
*t = (struct task_struct *) tarray_freelist; // 把當前指針當next用,指向空閒鏈表的第一項(多是NULL)
tarray_freelist = t; // tarray_freelist指向當前項
函數get_free_taskslot:
tslot = tarray_freelist; // *tslot是第一個空閒項
tarray_freelist = (struct task_struct **) *tslot; // *tslot的內容是下一個空閒項
return tslot;

各類各樣的進程指針:在進程描述符中有不少task_struct的指針,用於把它鏈進不一樣的鏈表或樹中。最簡單的是next_task和prev_task,它們把當前系統中的全部進程鏈成一條雙向鏈表;其次是next_run和prev_run,它們把當前可運行的進程(state爲TASK_RUNNING,這和current不一樣,並不表示它正在運行,只表示它能夠被CPU調度運行而已)鏈成一條雙向鏈表,不過個人源碼裏並無做者所說的runqueue指針頭,好象是init_task取代了runqueue的位置;pidhash_next和pidhash_pprev是用來鏈進以進程號索引的hash表的,由於大多調用都使用進程號標識進程,因此須要創建一個hash表來加快以進程號來查找進程的速度;最後是p_opptr,p_pptr,p_cptr,p_ysptr,p_osptr,這些是用來標識進程的父子,兄弟等樹狀關係的,做者書中的圖已經很清楚了,再也不須要我多說了。

等待隊列:通常來講,當某個任務不能立刻完成時,kernel不會陪着進程在那死等,它只是簡單把進程掛進某個隊列,而後繼續運行,等這個任務完成,kernel會從隊列中取出等待的進程,並把它鏈進可運行隊列中。等待隊列的結構體以下:
struct wait_queue {
struct task_struct * task;
struct wait_queue * next;
};
很簡單的結構,一個進程指針,一個next指針,應用時它將會是一個環形隊列,add_wait_queue加入一項,remove_wait_queue移去新舊的一項,都不是很難理解。麻煩的是它的init_waitqueue,內容爲
#define WAIT_QUEUE_HEAD(x) ((struct wait_queue *)((x)-1))
static inline void init_waitqueue(struct wait_queue **q)
{
*q = WAIT_QUEUE_HEAD(q);
}
結合做者的解釋,它其實是把當前隊列指針加上前面的四個字節假設爲一項了,而後"這一項"的next指針指向它本身。這個方法代碼倒很簡單,可是我卻不是很喜歡,可讀性實在有點。。。若是是我,寧願加一空項作表頭。
sleep和wakeup:剛纔所說的kernel把進程掛進隊通常是經過sleep_on來作的,而取出隊列是經過wake_up來作的。如今來看看它們是怎麼運行的吧。好比你要讀取軟盤內容,指令發出去了,但要等好久纔有迴應,這時會調用sleep_on把你的進程標識爲TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE狀態,而後把進程掛在某一個隊列上,而後調用schedule,這樣就會調度其它狀態爲TASK_RUNNING的進程繼續運行。當指令完成時,好比軟盤的內容已經讀到內存中了,這時可能會產生一箇中斷,這個中斷會從等待隊列中把你的進程取出來,標識爲TASK_RUNNING,而後放入可運行隊列,又過了若干時間,你的進程真的開始運行了,這時會執行sleep_on中schedule後的語句,即把進程從進程從等待隊列中移出去,而後就能夠執行下面的操做了,這時你要讀的內容已經讀出來了。

進程限制:誰都不但願某個用戶的進程會佔用全部的內存,全部的CPU,全部的任何資源,這樣就要對進程有所限制,kernel用了這樣一個結構:
struct rlimit {
long rlim_cur;
long rlim_max;
};
其中rlim_cur爲進程如今用到的資源數,rlim_max爲進程可用的最大資源數。
結構task_struct中有一項爲struct rlimit rlim[RLIM_NLIMITS],其中RLIM_NLIMITS爲10,即你對進程能夠有10種限制,這10種限制做者有講解的,我也不說了。

question:
一、個人印象中,在get_current中,esp應該是棧頂指針,並且隨時會變的,用它去減去8K,能獲得正確的地址嗎?


標題   Re: 新兵筆記--ULK(C3) Process Descriptor [re: Big John]
做者 lucian_yao (addict)
時間 05/20/01 09:16 AM


1應該是棧頂向下8K對齊獲得task_struct指針。
2在2.4中最大進程數沒有了,因爲基本不用TSS結構,因此不受GDT大小限制。

標題   Re: 新兵筆記--ULK(C3) Process Descriptor [re: lucian_yao]
做者 Big John (stranger )
時間 05/22/01 04:24 PM

一、是個人錯,把
__asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
中的andl當作addl了,因此百思而不得,呵。其實很簡單,系統分配進程描述符時,都是偶數頁對齊的,把sp後面的13位清0,固然也就是描述符的位置了。:)
二、2.4對進程沒有限制,那固然就不會再用task_struct的數組了,那它是怎麼組織的呢?不會是鏈表吧。

標題   Re: 新兵筆記--ULK(C3) Process Descriptor [re: Big John]
做者 iobject (stranger)
時間 05/29/01 04:08 PM

static inline struct task_struct * get_current(void)
{
struct task_struct *current;
__asm__("andl %%esp,%0; ":"=r" (current) : "" (~8191UL));
return current;
}
對於,%0,從語法上彷佛是指current,可是這樣的話這句話就說不通了,難道我對%0的理解有錯嗎
哪位指點一下,謝謝!


標題   Re: 新兵筆記--ULK(C3) Process Descriptor [re: iobject]
做者 Big John (stranger )
時間 05/29/01 05:33 PM

 

asm ("combine %2,%0" : "=r" (foo) : "0" (foo), "g" (bar));

The constraint `"0"' for operand 1 says that it must occupy the same
location as operand 0. A digit in constraint is allowed only in an
input operand and it must refer to an output operand.

這段來自gcc的info,大概意思是說,%1和%0將佔用同一個寄存器,因此引用%0也就是引用%1了。
這樣看來
__asm__("andl %%esp,%0; ":"=r" (current) : "0" (~8191UL));
展開應該是這樣的:
movl $(~8191UL),%eax
#APP
andl %esp, %eax
#NO_APP
movl %eax,current
我也是現學現用,不知道對不對。

[目錄]


init進程從內核態切換到用戶態


標題   init進程如何從內核態切換到用戶態。
做者 chstar (stranger )
時間 03/08/01 01:24 PM

 

init進程從內核態切換到用戶態。

//謝謝lucian_yao 邀請,在此灌水一篇

你們都知道如何產生一個新的進程。
經過sys_fork,以後再調用sys_execve
系統初啓後(核心態)的第一個用戶態進程是init。
這要涉及到內層(特權級高)向外層(特權級低)轉移的問題。
一般狀況下,內核是不會調用用戶層的代碼,要想實現這逆向的轉移,通常作法是在用戶進程的核心棧(tss->esp0)壓入用戶態的SS,ESP,EFLAGS,CS,EIP,假裝成用戶進程是經過陷阱門進入核心態,以後經過iret返回用戶態。
那麼linux 2.2.14中的用戶態進程init是如何實現的?

首先在kernel_thread(init...)函數中,
利用系統調用sys_clone fork出一個內核級進程(此時要給該進程分配核心棧<-esp0),以後call init函數,init函數還會再起幾個kernel_thread,而後會加載/sbin/init(經過execve調用)
在sys_execve中,要完成內核態到用戶態的轉移。
大致流程是sys_execve-->do_execve-->load_elf_binary()
-->do_load_elf_binary()-->do_mmap()
start_thread(reg,newip,newsp) (processor.h)
請你們關注do_mmap()及start_thread()很重要哦
do_mmap完成從文件虛擬空間到內存虛擬空間的映射。
而start_thread就是要在進程核心棧中的相應位置填入進程用戶態的xss,esp and xcs,eip.
最後進程從ret_from_sys_call返回,iret指令從核心棧pop出xcs, eip完成特權及指令的轉移, pop出 xss,esp,完成堆棧的切換。

以上我也是剛看代碼悟出的,若有不對之處,還望高手指出。

[目錄]


SET_LINKS


宏:SET_LINKS(p)將進程p插入到進程系中

struct task_struct {
  struct task_struct *next_task, *prev_task;
...
  struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr,*p_osptr;        ...};
next_task和prev_task 爲描述進程前後關係的環形隊列
p_opptr        指向原始的父進程
p_pptr        指向當前的父進程
p_cptr        指向最年輕的子進程
p_ysptr        指向弟進程
p_osptr        指向兄進程

include/linux/sched.h

#define SET_LINKS(p) do {
/
        (p)->next_task = &init_task;
/ 進程p的下一個進程是初始化進程
        (p)->prev_task = init_task.prev_task;
/ 進程p的前一個進程是初始化進程的前一個進程
        init_task.prev_task->next_task = (p);
/ 進程p的進一進程指向p
        init_task.prev_task = (p);
/初始化進程的前一進程指向p; 即將進程p加入到環形進程隊列的尾部
        (p)->p_ysptr = NULL; / 進程p如今是最年輕的進程
        if (((p)->p_osptr = (p)->p_pptr->p_cptr) != NULL)
                (p)->p_osptr->p_ysptr = p;
/ 原來的最年輕進程變成p的兄進程
        (p)->p_pptr->p_cptr = p; / 父進程指向新的子進程p
} while (0)

[目錄]


REMOVE_LINKS


宏:REMOVE_LINKS(p)將進程p從進程系中刪除

struct task_struct {
  struct task_struct *next_task, *prev_task;
...
  struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr,*p_osptr;        ...};
next_task和prev_task 爲描述進程前後關係的環形隊列
p_opptr        指向原始的父進程
p_pptr        指向當前的父進程
p_cptr        指向最年輕的子進程
p_ysptr        指向弟進程
p_osptr        指向兄進程


include/linux/sched.h
#define REMOVE_LINKS(p) do { /
        (p)->next_task->prev_task = (p)->prev_task;
/ 讓進程p的下一進程指向p的前一進程
        (p)->prev_task->next_task = (p)->next_task;
/ 讓進程p的前一進程指向p的下一進程
        if ((p)->p_osptr)
/ 若是進程p有兄進程,則讓兄進程指向p的弟進程
        (p)->p_osptr->p_ysptr = (p)->p_ysptr;
        if ((p)->p_ysptr)
/ 若是進程p有弟進程,則讓弟進程指向p的兄進程
                (p)->p_ysptr->p_osptr = (p)->p_osptr; /
        else / 若是p沒有弟進程,說明p最年輕,則讓父進程指向p的兄進程                (p)->p_pptr->p_cptr = (p)->p_osptr;
/
} while (0)

[目錄]


get_wchan()


get_wchan()給出了某個睡眠進程schedule()的調用點.

; arch/i386/kernel/process.c
unsigned long get_wchan(struct task_struct *p)
{
        unsigned long ebp, esp, eip;
        unsigned long stack_page;
        int count = 0;
        if (!p || p == current || p->state == TASK_RUNNING)
                return 0;
        stack_page = (unsigned long)p;
        esp = p->thread.esp; 取switch_to以前內核堆棧指針
        if (!stack_page || esp  8188+stack_page)
                return 0;
        /* include/asm-i386/system.h:switch_to() pushes ebp last. */
        ebp = *(unsigned long *) esp; 取保存在切換現場的schedule的ebp
        do {
                if (ebp  8184+stack_page)
                        return 0;
                eip = *(unsigned long *) (ebp+4);
                ; (ebp+0)爲上一級函數的ebp,(ebp+4)爲schedule()的返回地址
                ; kernel/sched.c編繹加了-fno-omit-frame-pointer編繹標誌,就是在這兒起做用.
                ; first_sched和last_sched是schedule()函數所在的地址範圍
                if (eip = last_sched)
                        return eip;
                ebp = *(unsigned long *) ebp;
        } while (count++         return 0;
}

如今的問題是,在什麼狀況下須要用count循環幾回? 現有的代碼好象不須要循環.

 

 

[目錄]


sigframe的結構


struct pt_regs {
        long ebx;
        long ecx;
        long edx;
        long esi;
        long edi;
        long ebp;
        long eax;
        int  xds;
        int  xes;
        long orig_eax;
        long eip;
        int  xcs;
        long eflags;
        long esp;
        int  xss;
};
typedef void (*__sighandler_t)(int);
struct sigaction {
        __sighandler_t sa_handler; 用戶的信號處理函數指針
        unsigned long sa_flags;
        void (*sa_restorer)(void); 用戶自定義的信號恢復函數指針
        sigset_t sa_mask;
};

struct k_sigaction {
        struct sigaction sa;
};
struct exec_domain {
        const char *name;
        lcall7_func handler;
        unsigned char pers_low, pers_high;
        unsigned long * signal_map;
        unsigned long * signal_invmap;
        struct module * module;
        struct exec_domain *next;
};
struct sigcontext {
        unsigned short gs, __gsh;
        unsigned short fs, __fsh;
        unsigned short es, __esh;
        unsigned short ds, __dsh;
        unsigned long edi;
        unsigned long esi;
        unsigned long ebp;
        unsigned long esp;
        unsigned long ebx;
        unsigned long edx;
        unsigned long ecx;
        unsigned long eax;
        unsigned long trapno;
        unsigned long err;
        unsigned long eip;
        unsigned short cs, __csh;
        unsigned long eflags;
        unsigned long esp_at_signal;
        unsigned short ss, __ssh;
        struct _fpstate * fpstate;
        unsigned long oldmask;
        unsigned long cr2;
};
struct _fpstate {

        unsigned long cw;
        unsigned long        sw;
        unsigned long        tag;
        unsigned long        ipoff;
        unsigned long        cssel;
        unsigned long        dataoff;
        unsigned long        datasel;
        struct _fpreg        _st[8];
        unsigned short        status;
        unsigned short        magic;


        unsigned long        _fxsr_env[6];
        unsigned long        mxcsr;
        unsigned long        reserved;
        struct _fpxreg        _fxsr_st[8];
        struct _xmmreg        _xmm[8];
        unsigned long        padding[56];
};
struct sigframe
{
        char *pretcode; 指向retcode
        int sig; sa_handler的sig參數
        struct sigcontext sc; CPU狀態
        struct _fpstate fpstate;若是進程使用過FPU的話保存FPU狀態
        unsigned long extramask[(64  / 32 ) -1];
        char retcode[8]; "popl % eax; movl $__NR_sigreturn,% eax; int $0x80"
};
static void setup_frame(int sig, struct k_sigaction *ka,
                        sigset_t *set, struct pt_regs * regs)
{
        struct sigframe *frame;
        int err = 0;

        ;取信號幀的起始地址
        frame = get_sigframe(ka, regs, sizeof(*frame));
        ;檢測frame指針是否越界
        if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
                goto give_sigsegv;
        ;每一個進程能夠對應於不一樣的運行域,若是須要的話就進行相應的信號轉換
        err |= __put_user((current->exec_domain
                            current->exec_domain->signal_invmap
                            sig                            ? current->exec_domain->signal_invmap[sig]
                           : sig),

        if (err)
                goto give_sigsegv;
        ;繼續在用戶堆棧上填充sigframe結構
        err |= setup_sigcontext(  regs, set->sig[0]);
        if (err)
                goto give_sigsegv;
        ;若是系統信號集的描述字多於1個的話,在extramask在保存多出來的部分,
        ;set->sig[0]已在sigframe->sc.oldmask保存
        if (_NSIG_WORDS > 1) {
                err |= __copy_to_user(frame->extramask,
                                      sizeof(frame->extramask));
        }
        if (err)
                goto give_sigsegv;

        /* Set up to return from userspace.  If provided, use a stub
           already in userspace.  */
        if (ka->sa.sa_flags  SA_RESTORER) {
                ; 若是用戶提供了信號的恢復代碼
                err |= __put_user(ka->sa.sa_restorer,
        } else {

                err |= __put_user(frame->retcode,
                /* This is popl % eax ; movl $,% eax ; int $0x80 */
                err |= __put_user(0xb858, (short *)(frame->retcode+0));
                err |= __put_user(__NR_sigreturn, (int *)(frame->retcode+2));
                err |= __put_user(0x80cd, (short *)(frame->retcode+6));
                ;popl % eax 退掉棧頂上frame->sig來與sys_sigreturn相對應
        }

        if (err)
                goto give_sigsegv;

        /* Set up registers for signal handler */
        regs->esp = (unsigned long) frame;
        regs->eip = (unsigned long) ka->sa.sa_handler;
        ; 一返回用戶進程,信號處理代碼就開始執行.
        set_fs(USER_DS);
        regs->xds = __USER_DS;
        regs->xes = __USER_DS;
        regs->xss = __USER_DS;
        regs->xcs = __USER_CS;
        regs->eflags  ~TF_MASK;

#if DEBUG_SIG
        printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p/n",
                current->comm, current->pid, frame, regs->eip, frame->pretcode);
#endif
        return;

give_sigsegv:
        ;若是創建sigframe出錯,忽略用戶的SIGSEGV過程,發出SIGSEGV信號強制進程退出
        if (sig == SIGSEGV)
                ka->sa.sa_handler = SIG_DFL;
        force_sig(SIGSEGV, current);
}
static inline int on_sig_stack(unsigned long sp)
{
        return (sp - current->sas_ss_sp sas_ss_size);
}
static inline void *
get_sigframe(struct k_sigaction *ka, struct pt_regs * regs, size_t frame_size)
{
        unsigned long esp;

        /* Default to using normal stack */
        esp = regs->esp; 用戶進程中的堆棧指針

        /* This is the X/Open sanctioned signal stack switching.  */
        if (ka->sa.sa_flags  SA_ONSTACK) {
                if (! on_sig_stack(esp))
                ; 若是esp sas_ss_sp + current->sas_ss_size)
                        esp = current->sas_ss_sp + current->sas_ss_size;
        }

        /* This is the legacy signal stack switching. */
        else if ((regs->xss  0xffff) != __USER_DS
                !(ka->sa.sa_flags  SA_RESTORER)
                ka->sa.sa_restorer) {
                ; 若是ss與ds不等,沒有SA_RESTORER標誌,但ka->sa.sa_restorer不爲0,
                ; 表示sa_restorer是一個用戶定義的堆棧指針
                esp = (unsigned long) ka->sa.sa_restorer;
        }
        ; sigframe在8字節邊界上對齊
        return (void *)((esp - frame_size)  -8ul);
}

static int
setup_sigcontext(struct sigcontext *sc, struct _fpstate *fpstate,
                struct pt_regs *regs, unsigned long mask)
{
        int tmp, err = 0;

        tmp = 0;
        __asm__("movl %%gs,%0" : "=r"(tmp): "0"(tmp));
        err |= __put_user(tmp, (unsigned int *)
        __asm__("movl %%fs,%0" : "=r"(tmp): "0"(tmp));
        err |= __put_user(tmp, (unsigned int *)

        err |= __put_user(regs->xes, (unsigned int *)
        err |= __put_user(regs->xds, (unsigned int *)
        err |= __put_user(regs->edi,
        err |= __put_user(regs->esi,
        err |= __put_user(regs->ebp,
        err |= __put_user(regs->esp,
        err |= __put_user(regs->ebx,
        err |= __put_user(regs->edx,
        err |= __put_user(regs->ecx,
        err |= __put_user(regs->eax,
        err |= __put_user(current->thread.trap_no,
        err |= __put_user(current->thread.error_code,
        err |= __put_user(regs->eip,
        err |= __put_user(regs->xcs, (unsigned int *)
        err |= __put_user(regs->eflags,
        err |= __put_user(regs->esp,
        err |= __put_user(regs->xss, (unsigned int *)
        ; 每一步都很謹慎
        tmp = save_i387(fpstate);
        if (tmp           err = 1;
        else
          err |= __put_user(tmp ? fpstate : NULL,
        ; sc->fpstate爲0表示該進程沒有使用過FPU
        /* non-iBCS2 extensions.. */
        err |= __put_user(mask,
        err |= __put_user(current->thread.cr2,

        return err;
}


sigframe就是調用用戶信號處理函數時進程堆棧指針和原來被中斷進程堆棧指針之間的數據塊.

 

 

[目錄]


rt_sigframe結構


struct rt_sigframe
{
        char *pretcode;
        int sig;
        struct siginfo *pinfo; 指向info
        void *puc; 指向uc
        struct siginfo info;
        struct ucontext uc;
        struct _fpstate fpstate;
        char retcode[8];
};
typedef struct sigaltstack {
        void *ss_sp;
        int ss_flags;
        size_t ss_size;
} stack_t;
struct ucontext {
        unsigned long          uc_flags;
        struct ucontext  *uc_link;
        stack_t  uc_stack;
        struct sigcontext uc_mcontext; 至關於sigframe中的sc
        sigset_t  uc_sigmask;
};
typedef struct siginfo {
        int si_signo;
        int si_errno;
        int si_code;

        union {
                int _pad[((128 /sizeof(int)) - 3) ];


                struct {
                        pid_t _pid;
                        uid_t _uid;
                } _kill;


                struct {
                        unsigned int _timer1;
                        unsigned int _timer2;
                } _timer;


                struct {
                        pid_t _pid;
                        uid_t _uid;
                        sigval_t _sigval;
                } _rt;


                struct {
                        pid_t _pid;
                        uid_t _uid;
                        int _status;
                        clock_t _utime;
                        clock_t _stime;
                } _sigchld;


                struct {
                        void *_addr;
                } _sigfault;


                struct {
                        int _band;
                        int _fd;
                } _sigpoll;
        } _sifields;
} siginfo_t;

; setup_rt_frame與setup_frame相比多了一個siginfo_t參數
static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
                           sigset_t *set, struct pt_regs * regs)
{
        struct rt_sigframe *frame;
        int err = 0;

        frame = get_sigframe(ka, regs, sizeof(*frame));

        if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
                goto give_sigsegv;

        err |= __put_user((current->exec_domain
                                current->exec_domain->signal_invmap
                                sig                                ? current->exec_domain->signal_invmap[sig]
                           : sig),

        ; 初始化frame->info和frame->uc兩個指針
        err |= __put_user(
        err |= __put_user(
        ; 將系統siginfo結構拷貝給用戶
        err |= copy_siginfo_to_user( info);
        if (err)
                goto give_sigsegv;

        /* Create the ucontext.  */
        err |= __put_user(0,
        err |= __put_user(0,
        err |= __put_user(current->sas_ss_sp,
        err |= __put_user(sas_ss_flags(regs->esp),

        err |= __put_user(current->sas_ss_size,
        err |= setup_sigcontext(
                                regs, set->sig[0]);
        err |= __copy_to_user( set, sizeof(*set));
        if (err)
                goto give_sigsegv;

        /* Set up to return from userspace.  If provided, use a stub
           already in userspace.  */
        if (ka->sa.sa_flags  SA_RESTORER) {
                err |= __put_user(ka->sa.sa_restorer,
        } else {
                err |= __put_user(frame->retcode,
                /* This is movl $,% eax ; int $0x80 */
                err |= __put_user(0xb8, (char *)(frame->retcode+0));
                err |= __put_user(__NR_rt_sigreturn, (int *)(frame->retcode+1));
                err |= __put_user(0x80cd, (short *)(frame->retcode+5));
                ; 沒有popl % eax
        }

        if (err)
                goto give_sigsegv;

        /* Set up registers for signal handler */
        regs->esp = (unsigned long) frame;
        regs->eip = (unsigned long) ka->sa.sa_handler;

        set_fs(USER_DS);
        regs->xds = __USER_DS;
        regs->xes = __USER_DS;
        regs->xss = __USER_DS;
        regs->xcs = __USER_CS;
        regs->eflags  ~TF_MASK;

#if DEBUG_SIG
        printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p/n",
                current->comm, current->pid, frame, regs->eip, frame->pretcode);
#endif

        return;

give_sigsegv:
        if (sig == SIGSEGV)
                ka->sa.sa_handler = SIG_DFL;
        force_sig(SIGSEGV, current);
}
int copy_siginfo_to_user(siginfo_t *to, siginfo_t *from)
{
        if (!access_ok (VERIFY_WRITE, to, sizeof(siginfo_t)))
                return -EFAULT;
        if (from->si_code                 ; 將整個siginfo_t結構拷貝到用戶堆棧上的rt_sigframe中
                return __copy_to_user(to, from, sizeof(siginfo_t));
        else {
                int err;

                /* If you change siginfo_t structure, please be sure
                   this code is fixed accordingly.
                   It should never copy any pad contained in the structure
                   to avoid security leaks, but must copy the generic
                   3 ints plus the relevant union member.  */
                err = __put_user(from->si_signo,
                err |= __put_user(from->si_errno,
                err |= __put_user((short)from->si_code,
                /* First 32bits of unions are always present.  */
                err |= __put_user(from->si_pid,
                switch (from->si_code >> 16) {
                case __SI_FAULT >> 16:
                        break;
                case __SI_CHLD >> 16:
                        err |= __put_user(from->si_utime,
                        err |= __put_user(from->si_stime,
                        err |= __put_user(from->si_status,
                default:
                        err |= __put_user(from->si_uid,
                        break;
                /* case __SI_RT: This is not generated by the kernel as of now.  */
                }
                return err;
        }
}


由此能夠看出rt_sigframe是sigframe的擴展和優化,和sigframe相比,rt_sigframe多了siginfo結構,sigframe的sigcontext擴展爲ucontext,用戶的信號處理函數多了兩個參數pinfo和uc,這樣內核能夠將更多的信息傳遞給用戶.除此之外,在運行機制上它們沒有什麼本質的區別.

 

 

[目錄]


信號隊列的結構


每一個進程具備一個sigpending結構所描述的信號隊列,它有3個成員,head指向第一個sigqueue成員,tail指向最末的sigqueue成員的next指針,signal描述了此隊列中的信號集.

static int send_signal(int sig, struct siginfo *info, struct sigpending *signals);
將信號sig和對應的消息結構info添加到信號隊列signal中.
static int collect_signal(int sig, struct sigpending *list, siginfo_t *info);
返回信號sig在隊列list中的信息info.


struct task_struct {
        ...
        struct sigpending pending;
        ...
};
struct sigpending {
        struct sigqueue *head, **tail;
        sigset_t signal;
};
struct sigqueue {
        struct sigqueue *next;
        siginfo_t info;
};
// kernel/signal.c
static int send_signal(int sig, struct siginfo *info, struct sigpending *signals)
{
        struct sigqueue * q = NULL;

        /* Real-time signals must be queued if sent by sigqueue, or
           some other real-time mechanism.  It is implementation
           defined whether kill() does so.  We attempt to do so, on
           the principle of least surprise, but since kill is not
           allowed to fail with EAGAIN when low on memory we just
           make sure at least one signal gets delivered and don't
           pass on the info struct.  */

        if (atomic_read(q = kmem_cache_alloc(sigqueue_cachep, GFP_ATOMIC);
        }
        // nr_queued_signals和max_queued_signals用來限制全局sigqueue成員的數目
        if (q) {
                atomic_inc(
                q->next = NULL;
                *signals->tail = q;
                signals->tail =  tail老是指向最末的信號成員的next指針
                switch ((unsigned long) info) {
                        case 0: // info參數若是爲0,表示信號來源於當前用戶進程
                                q->info.si_signo = sig;
                                q->info.si_errno = 0;
                                q->info.si_code = SI_USER;
                                q->info.si_pid = current->pid;
                                q->info.si_uid = current->uid;
                                break;
                        case 1: // info參數若是爲1,表示信號來源於內核自己
                                q->info.si_signo = sig;
                                q->info.si_errno = 0;
                                q->info.si_code = SI_KERNEL;
                                q->info.si_pid = 0;
                                q->info.si_uid = 0;
                                break;
                        default: // 不然從info指針中拷貝信號
                                copy_siginfo( info);
                                break;
                }
        } else if (sig >= SIGRTMIN  info  (unsigned long)info != 1
                    info->si_code != SI_USER) {
                ; 若是該信號是內核發出的實時信號,就返回錯誤碼
                /*
                * Queue overflow, abort.  We may abort if the signal was rt
                * and sent by user using something other than kill().
                */
                return -EAGAIN;
        }

        sigaddset( sig); 將sig號標記在隊列的信號集上
        return 0;
}
static int collect_signal(int sig, struct sigpending *list, siginfo_t *info)
{
        if (sigismember( sig)) {
                /* Collect the siginfo appropriate to this signal.  */
                struct sigqueue *q, **pp;
                pp =  pp指向第一個信號成員的next指針
                while ((q = *pp) != NULL) {
                        if (q->info.si_signo == sig)
                                goto found_it;
                        pp =
                }

                /* Ok, it wasn't in the queue.  We must have
                   been out of queue space.  So zero out the
                   info.  */
                sigdelset( sig);
                info->si_signo = sig;
                info->si_errno = 0;
                info->si_code = 0;
                info->si_pid = 0;
                info->si_uid = 0;
                return 1;

found_it:
                // 將找到信號成員從信號隊列中刪除
                if ((*pp = q->next) == NULL)
                        list->tail = pp;

                /* Copy the sigqueue information and free the queue entry */
                copy_siginfo(info,
                kmem_cache_free(sigqueue_cachep,q);
                atomic_dec(

                /* Non-RT signals can exist multiple times.. */
                if (sig >= SIGRTMIN) {
                        while ((q = *pp) != NULL) {
                                if (q->info.si_signo == sig)
                                        goto found_another;
                                pp =
                        }
                }

                sigdelset( sig);
found_another:
                return 1;
        }
        return 0;
}

[目錄]


系統調用

 

[目錄]


系統調用的實現


   看了前面一篇提到系統調用源碼的文章,也與做者有着一樣
的迷惑,查看了一下相關的源代碼,又翻了一下書,有了一點
點心得。
    linux裏面的每一個系統調用是靠一些宏,,一張系統調用表,
一個系統調用入口來完成的。
函數。
    1.宏  就是前面做者提到的_syscallN(type,name,x...),
      N是系統調用所需的參數數目,type是返回類型,name即面向
用戶的系統調用函數名,x...是調用參數,個數即爲N。
     例如:
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) /
type name(type1 arg1,type2 arg2,type3 arg3) /
{ /
long __res; /
__asm__ volatile ("int $0x80" /
        : "=a" (__res) /
        : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), /
                  "d" ((long)(arg3))); /
if (__res>=0) /
        return (type) __res; /
errno=-__res; /
return -1; /
}
(這是2.0.33版本)
      這些宏定義於include/asm/Unistd.h,這就是爲何你在
程序中要包含這個頭文件的緣由。該文件中還以__NR_name的形式
定義了164個常數,這些常數就是系統調用函數name的函數指針在
系統調用表中的偏移量。
    2.系統調用表
      定義於entry.s的最後。
      這個表按系統調用號(即前面提到的__NR_name)排列了所
有系統調用函數的指針,以供系統調用入口函數查找。從這張表看
得出,linux給它所支持的系統調用函數取名叫sys_name。
    3.系統調用入口函數
      定義於entry.s:
ENTRY(system_call)
        pushl %eax                      # save orig_eax
        SAVE_ALL
#ifdef __SMP__
        ENTER_KERNEL
#endif
        movl $-ENOSYS,EAX(%esp)
        cmpl $(NR_syscalls),%eax
        jae ret_from_sys_call
        movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax
        testl %eax,%eax
        je ret_from_sys_call
#ifdef __SMP__
        GET_PROCESSOR_OFFSET(%edx)
        movl SYMBOL_NAME(current_set)(,%edx),%ebx
#else
        movl SYMBOL_NAME(current_set),%ebx
#endif
        andl $~CF_MASK,EFLAGS(%esp)
        movl %db6,%edx
        movl %edx,dbgreg6(%ebx)
        testb $0x20,flags(%ebx)
        jne 1f
        call *%eax
        movl %eax,EAX(%esp)
        jmp ret_from_sys_call
   這段代碼現保存全部的寄存器值,而後檢查調用號(__NR_name)
是否合法(在系統調用表中查找),找到正確的函數指針後,就調用
該函數(即你真正但願內核幫你運行的函數)。運行返回後,將調用
ret_from_sys_call,這裏就是著名的進程調度時機之一。
    當在程序代碼中用到系統調用時,編譯器會將上面提到的
宏展開,展開後的代碼其實是將系統調用號放入ax後
移用int 0x80使處理器轉向系統調用入口,而後查找系統調用表,進
而由內核調用真正的功能函數。
    前面那篇文章中的問題我是這樣理解的:
   本身添加過系統調用的人可能知道,要在程序中使用本身的
系統調用,必須顯示地應用宏_syscallN。
    而對於linux預約義的系統調用,編譯器在預處理時自動加入宏
_syscall3(int,ioctl,arg1,arg2,arg3)並將其展開。因此,並非
ioctl自己是宏替換符,而是編譯器自動用宏聲明瞭ioctl這個函數。
    實際上我本身沒有給linux添加過系統調用,也沒有很仔細的看
源代碼,但願這方面的高手能多多指教。

[目錄]


添加系統調用


如何在Linux中添加新的系統調用

  系統調用是應用程序和操做系統內核之間的功能接口。其主要目的是使得用戶能夠
使用操做系統提供的有關設備管理、輸入/輸入系統、文件系統和進程控制、通訊以及存
儲管理等方面的功能,而沒必要了解系統程序的內部結構和有關硬件細節,從而起到減輕
用戶負擔和保護系統以及提升資源利用率的做用。
  Linux操做系統做爲自由軟件的表明,它優良的性能使得它的應用日益普遍,不只得
到專業人士的確定,並且商業化的應用也是如火如荼。在Linux中,大部分的系統調用包
含在Linux的libc庫中,經過標準的C函數調用方法能夠調用這些系統調用。那麼,對Li
nux的發燒友來講,如何在Linux中增長新的系統調用呢?
1 Linux系統調用機制
  在Linux系統中,系統調用是做爲一種異常類型實現的。它將執行相應的機器代碼指
令來產生異常信號。產生中斷或異常的重要效果是系統自動將用戶態切換爲核心態來對
它進行處理。這就是說,執行系統調用異常指令時,自動地將系統切換爲核心態,並安
排異常處理程序的執行。
  Linux用來實現系統調用異常的實際指令是:
  Int $0x80
  這一指令使用中斷/異常向量號128(即16進制的80)將控制權轉移給內核。爲達到
在使用系統調用時沒必要用機器指令編程,在標準的C語言庫中爲每一系統調用提供了一段
短的子程序,完成機器代碼的編程工做。事實上,機器代碼段很是簡短。它所要作的工
做只是將送給系統調用的參數加載到CPU寄存器中,接着執行int $0x80指令。而後運行
系統調用,系統調用的返回值將送入CPU的一個寄存器中,標準的庫子程序取得這一返回
值,並將它送回用戶程序。
  爲使系統調用的執行成爲一項簡單的任務,Linux提供了一組預處理宏指令。它們可
以用在程序中。這些宏指令取必定的參數,而後擴展爲調用指定的系統調用的函數。
  這些宏指令具備相似下面的名稱格式:
  _syscallN(parameters)
  其中N是系統調用所需的參數數目,而parameters則用一組參數代替。這些參數使宏
指令完成適合於特定的系統調用的擴展。例如,爲了創建調用setuid()系統調用的函
數,應該使用:
  _syscall1( int, setuid, uid_t, uid )
  syscallN( )宏指令的第1個參數int說明產生的函數的返回值的類型是整型,第2
個參數setuid說明產生的函數的名稱。後面是系統調用所須要的每一個參數。這一宏指令
後面還有兩個參數uid_t和uid分別用來指定參數的類型和名稱。
  另外,用做系統調用的參數的數據類型有一個限制,它們的容量不能超過四個字節
。這是由於執行int $0x80指令進行系統調用時,全部的參數值都存在32位的CPU寄存器
中。使用CPU寄存器傳遞參數帶來的另外一個限制是能夠傳送給系統調用的參數的數目。這
個限制是最多能夠傳遞5個參數。因此Linux一共定義了6個不一樣的_syscallN()宏指令
,從_syscall0()、_syscall1()直到_syscall5()。
  一旦_syscallN()宏指令用特定系統調用的相應參數進行了擴展,獲得的結果是一
個與系統調用同名的函數,它能夠在用戶程序中執行這一系統調用。

2 添加新的系統調用
  若是用戶在Linux中添加新的系統調用,應該遵循幾個步驟才能添加成功,下面幾個
步驟詳細說明了添加系統調用的相關內容。
(1) 添加源代碼
  第一個任務是編寫加到內核中的源程序,即將要加到一個內核文件中去的一個函數
,該函數的名稱應該是新的系統調用名稱前面加上sys_標誌。假設新加的系統調用爲my
call(int number),在/usr/src/linux/kernel/sys.c文件中添加源代碼,以下所示:
  asmlinkage int sys_mycall(int number)
  {
  return number;
  }
  做爲一個最簡單的例子,咱們新加的系統調用僅僅返回一個整型值。
(2) 鏈接新的系統調用
  添加新的系統調用後,下一個任務是使Linux內核的其他部分知道該程序的存在。爲
了從已有的內核程序中增長到新的函數的鏈接,須要編輯兩個文件。
  在咱們所用的Linux內核版本(RedHat 6.0,內核爲2.2.5-15)中,第一個要修改的
文件是:
  /usr/src/linux/include/asm-i386/unistd.h
  該文件中包含了系統調用清單,用來給每一個系統調用分配一個惟一的號碼。文件中
每一行的格式以下:
  #define __NR_name NNN
  其中,name用系統調用名稱代替,而NNN則是該系統調用對應的號碼。應該將新的系
統調用名稱加到清單的最後,並給它分配號碼序列中下一個可用的系統調用號。咱們的
系統調用以下:
  #define __NR_mycall 191
  系統調用號爲191,之因此係統調用號是191,是由於Linux-2.2內核自身的系統調用
號碼已經用到190。
  第二個要修改的文件是:
  /usr/src/linux/arch/i386/kernel/entry.S
  該文件中有相似以下的清單:
  .long SYMBOL_NAME()
  該清單用來對sys_call_table[]數組進行初始化。該數組包含指向內核中每一個系統
調用的指針。這樣就在數組中增長了新的內核函數的指針。咱們在清單最後添加一行:

  .long SYMBOL_NAME(sys_mycall)

(3) 重建新的Linux內核
  爲使新的系統調用生效,須要重建Linux的內核。這須要以超級用戶身份登陸。
  #pwd
  /usr/src/linux
  #
  超級用戶在當前工做目錄(/usr/src/linux)下,才能夠重建內核。
  #make config
  #make dep
  #make clearn
  #make bzImage
  編譯完畢後,系統生成一可用於安裝的、壓縮的內核映象文件:
  /usr/src/linux/arch/i386/boot/bzImage
(4) 用新的內核啓動系統
  要使用新的系統調用,須要用重建的新內核從新引導系統。爲此,須要修改/etc/l
ilo.conf文件,在咱們的系統中,該文件內容以下:
  boot=/dev/hda
  map=/boot/map
  install=/boot/boot.b
  prompt
  timeout=50
  image=/boot/vmlinuz-2.2.5-15
  label=linux
  root=/dev/hdb1
  read-only
  other=/dev/hda1
  label=dos
  table=/dev/had
  首先編輯該文件,添加新的引導內核:
  image=/boot/bzImage-new
  label=linux-new
  root=/dev/hdb1
  read-only
  添加完畢,該文件內容以下所示:
  boot=/dev/hda
  map=/boot/map
  install=/boot/boot.b
  prompt
  timeout=50
  image=/boot/bzImage-new
  label=linux-new
  root=/dev/hdb1
  read-only
  image=/boot/vmlinuz-2.2.5-15
  label=linux
  root=/dev/hdb1
  read-only
  other=/dev/hda1
  label=dos
  table=/dev/hda
  這樣,新的內核映象bzImage-new成爲缺省的引導內核。
  爲了使用新的lilo.conf配置文件,還應執行下面的命令:
  #cp /usr/src/linux/arch/i386/boot/zImage /boot/bzImage-new
  其次配置lilo:
  # /sbin/lilo
  如今,當從新引導系統時,在boot:提示符後面有三種選擇:linux-new 、 linux、
dos,新內核成爲缺省的引導內核。
  至此,新的Linux內核已經創建,新添加的系統調用已成爲操做系統的一部分,從新
啓動Linux,用戶就能夠在應用程序中使用該系統調用了。
(5)使用新的系統調用
  在應用程序中使用新添加的系統調用mycall。一樣爲實驗目的,咱們寫了一個簡單
的例子xtdy.c。
  /* xtdy.c */
  #include <linux/unistd.h>
  _syscall1(int,mycall,int,ret)
  main()
  {
  printf("%d /n",mycall(100));
  }
  編譯該程序:
  # cc -o xtdy xtdy.c
  執行:

  # xtdy
  結果:
  # 100
  注意,因爲使用了系統調用,編譯和執行程序時,用戶都應該是超級用戶身份。
(文/程仁田)

[目錄]


系統調用簡述


Linux的系統調用
系統調用響應函數的函數名約定
函數名以「sys_」開頭,後跟該系統調用的名字,由此構成164個形似sys_name()的函數名。所以,系統調用ptrace()的響應函數是sys_ptrace() (kernel/ptrace.c)。
系統調用號
文件include/asm/unistd.h爲每一個系統調用規定了惟一的編號:
#define __NR_setup                  0
#define __NR_exit                            1
#define __NR_fork                  2
…        …
#define __NR_ptrace                  26
以系統調用號__NR_name做爲下標,找出系統調用表sys_call_table (arch/i386/kernel/entry.S)中對應表項的內容,正好就是該系統調用的響應函數sys_name的入口地址。
系統調用表
系統調用表sys_call_table (arch/i386/kernel/entry.S)形如:
ENTRY(sys_call_table)
        .long SYMBOL_NAME(sys_setup)                /* 0 */
        .long SYMBOL_NAME(sys_exit)
        .long SYMBOL_NAME(sys_fork)
                …        …
                .long SYMBOL_NAME(sys_stime)                /* 25 */
        .long SYMBOL_NAME(sys_ptrace)
                …        …
sys_call_table記錄了各sys_name函數(共166項,其中2項無效)在表中的位子。有了這張表,很容易根據特定系統調用在表中的偏移量,找到對應的系統調用響應函數的入口地址。NR_syscalls(即256)表示最多可容納的系統調用個數。這樣,餘下的90項就是可供用戶本身添加的系統調用空間。
從ptrace系統調用命令到INT  0X80中斷請求的轉換
宏定義syscallN()(include/asm/unistd.h)用於系統調用的格式轉換和參數的傳遞。
#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) /
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) /
{ /
long __res; /
__asm__ volatile ("int $0x80" /
        : "=a" (__res) /
        : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), /
          "d" ((long)(arg3)),"S" ((long)(arg4))); /
__syscall_return(type,__res); /
}
N取0與5之間任意整數。參數個數爲N的系統調用由syscallN負責格式轉換和參數傳遞。例如,ptrace()有四個參數,它對應的格式轉換宏就是syscall4()。
syscallN()第一個參數說明響應函數返回值的類型,第二個參數爲系統調用的名稱(即name),其他的參數依次爲系統調用參數的類型和名稱。例如,
_syscall4(int, ptrace, long request, long pid, long addr, long data)
說明了系統調用命令
int sys_ptrace(long request, long pid, long addr, long data)
宏定義的餘下部分描述了啓動INT 0X80和接收、判斷返回值的過程。也就是說,以系統調用號對EAX寄存器賦值,啓動INT 0X80。規定返回值送EAX寄存器。函數的參數壓棧,壓棧順序見下表:
參數        參數在堆棧的位置        傳遞參數的寄存器
arg1        00(%esp)        ebx
arg2        04(%esp)        ecx
arg3        08(%esp)        edx
arg4        0c(%esp)        esi
arg5        10(%esp)        edi
若INT 0X80的返回值非負,則直接按類型type返回;不然,將INT 0X80的返回值取絕對值,保留在errno變量中,返回-1。
系統調用功能模塊的初始化
對系統調用的初始化也即對INT 0X80的初始化。系統啓動時,彙編子程序setup_idt(arch/i386/kernel/head.S)準備了張256項的idt 表,由start_kernel()(init/main.c)、trap_init()(arch/i386/kernel/traps.c)調用的C語言宏定義set_system_gate(0x80, &system_call)(include/asm/system.h)設置0X80號軟中斷的服務程序爲system_call。system_call(arch/i386/kernel/entry.S)就是全部系統調用的總入口。
LINUX內部是如何分別爲各類系統調用服務的
當進程須要進行系統調用時,必須以C語言函數的形式寫一句系統調用命令。當進程執行到用戶程序的系統調用命令時,實際上執行了由宏命令_syscallN()展開的函數。系統調用的參數由各通用寄存器傳遞。而後執行INT 0X80,以核心態進入入口地址system_call。
ENTRY(system_call)
        pushl %eax                        # save orig_eax
        SAVE_ALL
#ifdef __SMP__
        ENTER_KERNEL
#endif
        movl $-ENOSYS,EAX(%esp)
        cmpl $(NR_syscalls),%eax
        jae ret_from_sys_call
        movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax
        testl %eax,%eax
        je ret_from_sys_call
#ifdef __SMP__
        GET_PROCESSOR_OFFSET(%edx)
        movl SYMBOL_NAME(current_set)(,%edx),%ebx
#else
        movl SYMBOL_NAME(current_set),%ebx
#endif
        andl $~CF_MASK,EFLAGS(%esp)        # clear carry - assume no errors
        movl %db6,%edx
        movl %edx,dbgreg6(%ebx)  # save current hardware debugging status
        testb $0x20,flags(%ebx)                # PF_TRACESYS
        jne 1f
        call *%eax
        movl %eax,EAX(%esp)                # save the return value
        jmp ret_from_sys_call
從system_call入口的彙編程序的主要功能是:
保存寄存器當前值(SAVE_ALL);
檢驗是否爲合法的系統調用;
根據系統調用表_sys_call_table和EAX持有的系統調用號找出並轉入系統調用響應函數;
從該響應函數返回後,讓EAX寄存器保存函數返回值,跳轉至ret_from_sys_call(arch/i386/kernel/entry.S)。
最後,在執行位於用戶程序中系統調用命令後面餘下的指令以前,若INT 0X80的返回值非負,則直接按類型type返回;不然,將INT 0X80的返回值取絕對值,保留在errno變量中,返回-1。

[目錄]


增長系統調用


深刻LINUX內核:爲你的LINUX增長一條系統調用
  充分利用LINUX開放源碼的特性,咱們能夠輕易地對它進行修改,使咱們可以隨心所
欲駕馭LINUX,完成一個真正屬於本身的操做系統,這種感受使無與倫比的,下面經過爲
LINUX增長一個系統調用來展現LINUX做爲一個開放源碼操做系統的強大魅力。
  首先,讓咱們簡單地分析一下LINUX中與系統調用的相關的部分:
  LINUX的系統調用的總控程序是system_call,它是LINUX系統中全部系統調用的總入
口,這個system_call是做爲一箇中斷服務程序掛在中斷0x80上,系統初始化時經過voi
d init trap_init(void)調用一個宏set_system_ gate(SYSCALL_VERCTOR,&system_ca
ll)來對IDT表進行初始化,在0x80對應的中斷描述符處填入system_call函數的地址,其
中宏SYSCALL_VERCTOR就是0x80。
  當發生一條系統調用時,由中斷總控程序保存處理機狀態,檢查調用參數的合法性
,而後根據系統調用向量在sys_call_table中找到相應的系統服務例程的地址,而後執
行該服務例程,完成後恢復中斷總控程序所保存的處理機狀態,返回用戶程序。
  系統服務例程通常定義於kernel/sys.c中,系統調用向量定義在include/asm-386/
unistd.h中,而sys_call _table表則定義在arch/i386/kernel/entry.S文件裏。
  如今咱們知道增長一條系統調用咱們首先要添加服務例程實現代碼,而後在進行對
應向量的申明,最後固然還要在sys_call_table表中增長一項以指明服務例程的入口地
址。
  OK,有了以上簡單的分析,如今咱們能夠開始進行源碼的修改,假設咱們須要添加
一條系統調用計算兩個整數的平方和,系統調用名爲add2,咱們須要修改三個文件:ker
nel/sys.c , arch/i386/kernel/entry.S 和 include/asm-386/unistd.h。
  一、修改kernel/sys.c ,增長服務例程代碼:
  asmlinkage int sys_add2(int a , int b)
    {
      int c=0;
      c=a*a+b*b;
      return c;
    }
  二、修改include/asm-386/unistd.h ,對咱們剛纔增長的系統調用申明向量,以使
用戶或系統進程可以找到這條系統調用,修改後文件以下所示:
  .... .....
  #define _NR_sendfile   187
  #define _NR_getpmsg    188
  #define _NR_putmsg    189
  #define _NR_vfork     190
  #define _NR_add2     191   /* 這是咱們添加的部分,191即向量 */
  三、修改include/asm-386/unistd.h , 將服務函數入口地址加入 sys_call_table,
首先找到這麼一段:
  .... .....
  .long SYMBOL_NAME(sys_sendfile)
  .long SYMBOL_NAME(sys_ni_syscall) /* streams 1 */
  .long SYMBOL_NAME(sys_ni_syscall) /* streams 2 */
  .long SYMBOL_NAME(sys_vfork) /*190 */
  .rept NR_syscalls-190
  修改成以下:
  .... .....
  .long SYMBOL_NAME(sys_sendfile)
  .long SYMBOL_NAME(sys_ni_syscall) /* streams 1 */
  .long SYMBOL_NAME(sys_ni_syscall) /* streams 2 */
  .long SYMBOL_NAME(sys_vfork) /*190 */
  .long SYMBOL_NAME(sys_add2) <=咱們的系統調用
  .rept NR_syscalls-191 <=將190改成191
  OK,大功告成,如今只須要從新編譯你的LINUX內核,而後你的LINUX就有了一條新的
系統調用int add2(int a, int b)。
做者會員名:flinstone  E-MAIL:netbyte@263.net

[目錄]


mlock代碼分析


        系統調用mlock的做用是屏蔽內存中某些用戶進程所要求的頁。
        mlock調用的語法爲:
                int sys_mlock(unsigned long start, size_t len);
初始化爲:
        len=(len+(start &~PAGE_MASK)+ ~PAGE_MASK)&PAGE_MASK;
start &=PAGE_MASK;
其中mlock又調用do_mlock(),語法爲:
int do_mlock(unsigned long start, size_t len,int on);
初始化爲:
        len=(len+~PAGE_MASK)&PAGE_MASK;

    由mlock的參數可看出,mlock對由start所在頁的起始地址開始,長度爲len(注:len=(len+(start&~PAGE_MASK)+ ~PAGE_MASK)&PAGE_MASK)的內存區域的頁進行加鎖。
sys_mlock若是調用成功返回,這其中全部的包含具體內存區域的頁必須是常駐內存的,或者說在調用munlock 或 munlockall以前這部分被鎖住的頁面必須保留在內存。固然,若是調用mlock的進程終止或者調用exec執行其餘程序,則這部分被鎖住的頁面被釋放。經過fork()調用所建立的子進程不可以繼承由父進程調用mlock鎖住的頁面。
    內存屏蔽主要有兩個方面的應用:實時算法和高度機密數據的處理。實時應用要求嚴格的分時,好比調度,調度頁面是程序執行延時的一個主要因素。保密安全軟件常常處理關鍵字節,好比密碼或者密鑰等數據結構。頁面調度的結果是有可能將這些重要字節寫到外存(如硬盤)中去。這樣一些黑客就有可能在這些安全軟件刪除這些在內存中的數據後還能訪問部分在硬盤中的數據。        而對內存進行加鎖徹底能夠解決上述難題。
    內存加鎖不使用壓棧技術,即那些經過調用mlock或者mlockall被鎖住屢次的頁面能夠經過調用一次munlock或者munlockall釋放相應的頁面
    mlock的返回值分析:若調用mlock成功,則返回0;若不成功,則返回-1,而且errno被置位,進程的地址空間保持原來的狀態。返回錯誤代碼分析以下:
   ENOMEM:部分具體地址區域沒有相應的進程地址空間與之對應或者超出了進程所容許的最大可鎖頁面。
   EPERM:調用mlock的進程沒有正確的優先權。只有root進程才容許鎖住要求的頁面。
EINVAL:輸入參數len不是個合法的正數。


mlock所用到的主要數據結構和重要常量

1.mm_struct
struct mm_struct {
        int count;
        pgd_t * pgd; /* 進程頁目錄的起始地址,如圖2-3所示 */
        unsigned long context;
        unsigned long start_code, end_code, start_data, end_data;
        unsigned long start_brk, brk, start_stack, start_mmap;
        unsigned long arg_start, arg_end, env_start, env_end;
        unsigned long rss, total_vm, locked_vm;
        unsigned long def_flags;
        struct vm_area_struct * mmap;     /* 指向vma雙向鏈表的指針 */
        struct vm_area_struct * mmap_avl; /* 指向vma AVL樹的指針 */
        struct semaphore mmap_sem;
}
start_code、end_code:進程代碼段的起始地址和結束地址。
start_data、end_data:進程數據段的起始地址和結束地址。
arg_start、arg_end:調用參數區的起始地址和結束地址。
env_start、env_end:進程環境區的起始地址和結束地址。
rss:進程內容駐留在物理內存的頁面總數。


2. 虛存段(vma)數據結構:vm_area_atruct

虛存段vma由數據結構vm_area_atruct(include/linux/mm.h)描述:
struct vm_area_struct {
        struct mm_struct * vm_mm;        /* VM area parameters */
        unsigned long vm_start;
        unsigned long vm_end;
        pgprot_t vm_page_prot;
        unsigned short vm_flags;
/* AVL tree of VM areas per task, sorted by address */
        short vm_avl_height;
        struct vm_area_struct * vm_avl_left;
        struct vm_area_struct * vm_avl_right;
/* linked list of VM areas per task, sorted by address */
        struct vm_area_struct * vm_next;
/* for areas with inode, the circular list inode->i_mmap */
/* for shm areas, the circular list of attaches */
/* otherwise unused */
        struct vm_area_struct * vm_next_share;
        struct vm_area_struct * vm_prev_share;
/* more */
        struct vm_operations_struct * vm_ops;
        unsigned long vm_offset;
        struct inode * vm_inode;
        unsigned long vm_pte;                        /* shared mem */
};

vm_start;//所對應內存區域的開始地址
vm_end; //所對應內存區域的結束地址
vm_flags; //進程對所對應內存區域的訪問權限
vm_avl_height;//avl樹的高度
vm_avl_left; //avl樹的左兒子
vm_avl_right; //avl樹的右兒子
vm_next;// 進程所使用的按地址排序的vm_area鏈表指針
vm_ops;//一組對內存的操做
    這些對內存的操做是當對虛存進行操做的時候Linux系統必須使用的一組方法。好比說,當進程準備訪問某一虛存區域可是發現此區域在物理內存不存在時(缺頁中斷),就激發某種對內存的操做執行正確的行爲。這種操做是空頁(nopage)操做。當Linux系統按需調度可執行的頁面映象進入內存時就使用這種空頁(nopage)操做。
    當一個可執行的頁面映象映射到進程的虛存地址時,一組vm_area_struct結構的數據結構(vma)就會生成。每個vm_area_struct的數據結構(vma)表明可執行的頁面映象的一部分:可執行代碼,初始化數據(變量),非初始化數據等等。Linux系統能夠支持大量的標準虛存操做,當vm_area_struct數據結構(vma)一被建立,它就對應於一組正確的虛存操做。
    屬於同一進程的vma段經過vm_next指針鏈接,組成鏈表。如圖2-3所示,struct mm_struct結構的成員struct vm_area_struct * mmap  表示進程的vma鏈表的表頭。
    爲了提升對vma段 查詢、插入、刪除操做的速度,LINUX同時維護了一個AVL(Adelson-Velskii and Landis)樹。在樹中,全部的vm_area_struct虛存段均有左指針vm_avl_left指向相鄰的低地址虛存段,右指針vm_avl_right指向相鄰的高地址虛存段,如圖2-5。struct mm_struct結構的成員struct vm_area_struct * mmap_avl表示進程的AVL樹的根,vm_avl_height表示AVL樹的高度。
    對平衡樹mmap_avl的任何操做必須知足平衡樹的一些規則:
Consistency and balancing rulesJ(一致性和平衡規則):

tree->vm_avl_height==1+max(heightof(tree->vm_avl_left),heightof(
tree->vm_avl_right))
abs( heightof(tree->vm_avl_left) - heightof(tree->vm_avl_right) ) <= 1
foreach node in tree->vm_avl_left: node->vm_avl_key <= tree->vm_avl_key,        foreach node in tree->vm_avl_right: node->vm_avl_key >= tree->vm_avl_key.
        注:其中node->vm_avl_key= node->vm_end

對vma能夠進行加鎖、加保護、共享和動態擴展等操做。

3.重要常量
    mlock系統調用所用到的重要常量有:PAGE_MASK、PAGE_SIZE、PAGE_SHIFT、RLIMIT_MEMLOCK、VM_LOCKED、 PF_SUPERPRIV等。它們的值分別以下:
        PAGE_SHIFT                        12                                // PAGE_SHIFT determines the page size
        PAGE_SIZE                        0x1000                        //1UL<<PAGE_SHIFT
        PAGE_MASK                        ~(PAGE_SIZE-1)        //a very useful constant variable
        RLIMIT_MEMLOCK                8                                //max locked-in-memory address space
        VM_LOCKED                        0x2000                        //8*1024=8192, vm_flags的標誌之一。
        PF_SUPERPRIV                0x00000100                //512,

 

mlock系統調用代碼函數功能分析

下面對各個函數的功能做詳細的分析((1)和(2)在前面簡介mlock時已介紹過,並在後面有詳細的程序流程):
suser():若是用戶有效(即current->euid == 0        ),則設置進程標誌爲root優先權(current->flags |= PF_SUPERPRIV),並返回1;不然返回0。
find_vma(struct mm_struct * mm, unsigned long addr):輸入參數爲當前進程的mm、須要加鎖的開始內存地址addr。find_vma的功能是在mm的mmap_avl樹中尋找第一個知足mm->mmap_avl->vm_start<=addr< mm->mmap_avl->vm_end的vma,若是成功則返回此vma;不然返回空null。
mlock_fixup(struct vm_area_struct * vma, unsigned long start, unsigned long end, unsigned int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要加鎖內存區域起始地址和結束地址、須要修改的標誌(0:加鎖,1:釋放鎖)。
merge_segments(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr):輸入參數爲當前進程的mm、須要加鎖的開始內存地址start_addr和結束地址end_addr。merge_segments的功能的是盡最大可能歸併相鄰(即內存地址偏移量連續)並有相同屬性(包括vm_inode,vm_pte,vm_ops,vm_flags)的內存段,在這過程當中冗餘的vm_area_structs被釋放,這就要求vm_mmap鏈按地址大小排序(咱們不須要遍歷整個表,而只須要遍歷那些交叉或者相隔必定連續區域的鄰接vm_area_structs)。固然在缺省的狀況下,merge_segments是對vm_mmap_avl樹進行循環處理,有多少能夠合併的段就合併多少。
mlock_fixup_all(struct vm_area_struct * vma, int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要修改的標誌(0:加鎖,1:釋放鎖)。mlock_fixup_all的功能是根據輸入參數newflags修改此vma的vm_flags。
mlock_fixup_start(struct vm_area_struct * vma,unsigned long end, int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要加鎖內存區域結束地址、須要修改的標誌(0:加鎖,1:釋放鎖)。mlock_fixup_start的功能是根據輸入參數end,在內存中分配一個新的new_vma,把原來的vma分紅兩個部分: new_vma和vma,其中new_vma的vm_flags被設置成輸入參數newflags;而且按地址(new_vma->start和new_vma->end)大小序列把新生成的new->vma插入到當前進程mm的mmap鏈或mmap_avl樹中(缺省狀況下是插入到mmap_avl樹中)。
        注:vma->vm_offset+= vma->vm_start-new_vma->vm_start;
mlock_fixup_end(struct vm_area_struct * vma,unsigned long start, int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要加鎖內存區域起始地址、須要修改的標誌(0:加鎖,1:釋放鎖)。mlock_fixup_end的功能是根據輸入參數start,在內存中分配一個新的new_vma,把原來的vma分紅兩個部分:vma和new_vma,其中new_vma的vm_flags被設置成輸入參數newflags;而且按地址大小序列把new->vma插入到當前進程mm的mmap鏈或mmap_avl樹中。
        注:new_vma->vm_offset= vma->vm_offset+(new_vma->vm_start-vma->vm_start);
mlock_fixup_middle(struct vm_area_struct * vma,unsigned long start, unsigned long end, int newflags):輸入參數爲vm_mmap鏈中的某個vma、須要加鎖內存區域起始地址和結束地址、須要修改的標誌(0:加鎖,1:釋放鎖)。mlock_fixup_middle的功能是根據輸入參數start、end,在內存中分配兩個新vma,把原來的vma分紅三個部分:left_vma、vma和right_vma,其中vma的vm_flags被設置成輸入參數newflags;而且按地址大小序列把left->vma和right->vma插入到當前進程mm的mmap鏈或mmap_avl樹中。
        注:vma->vm_offset += vma->vm_start-left_vma->vm_start;
                right_vma->vm_offset += right_vma->vm_start-left_vma->vm_start;
kmalloc():將在後面3.3中有詳細討論。
insert_vm_struct(struct mm_struct *mm, struct vm_area_struct *vmp):輸入參數爲當前進程的mm、須要插入的vmp。insert_vm_struct的功能是按地址大小序列把vmp插入到當前進程mm的mmap鏈或mmap_avl樹中,而且把vmp插入到vmp->inode的i_mmap環(循環共享鏈)中。
avl_insert_neighbours(struct vm_area_struct * new_node,** ptree,** to_the_left,** to_the_right):輸入參數爲當前須要插入的新vma結點new_node、目標mmap_avl樹ptree、新結點插入ptree後它左邊的結點以及它右邊的結點(左右邊結點按mmap_avl中各vma->vma_end大小排序)。avl_insert_neighbours的功能是插入新vma結點new_node到目標mmap_avl樹ptree中,而且調用avl_rebalance以保持ptree的平衡樹特性,最後返回new_node左邊的結點以及它右邊的結點。
avl_rebalance(struct vm_area_struct *** nodeplaces_ptr, int count):輸入參數爲指向vm_area_struct指針結構的指針數據nodeplaces_ptr[](每一個元素表示須要平衡的mmap_avl子樹)、數據元素個數count。avl_rebalance的功能是從nodeplaces_ptr[--count]開始直到nodeplaces_ptr[0]循環平衡各個mmap_avl子樹,最終使整個mmap_avl樹平衡。
down(struct semaphore * sem):輸入參數爲同步(進入臨界區)信號量sem。down的功能根據當前信號量的設置狀況加鎖(阻止別的進程進入臨界區)並繼續執行或進入等待狀態(等待別的進程執行完成退出臨界區並釋放鎖)。
        down定義在/include/linux/sched.h中:
extern inline void down(struct semaphore * sem)
{
        if (sem->count <= 0)
                __down(sem);
        sem->count--;
}
up(struct semaphore * sem)輸入參數爲同步(進入臨界區)信號量sem。up的功能根據當前信號量的設置狀況(當信號量的值爲負數:表示有某個進程在等待使用此臨界區 )釋放鎖。
        up定義在/include/linux/sched.h中:
extern inline void up(struct semaphore * sem)
{
        sem->count++;
        wake_up(&sem->wait);
        }
kfree_s(a,b):kfree_s定義在/include/linux/malloc.h中:#define kfree_s(a,b) kfree(a)。而kfree()將在後面3.3中詳細討論。
avl_neighbours(struct vm_area_struct * node,* tree,** to_the_left,** to_the_right):輸入參數爲做爲查找條件的vma結點node、目標mmap_avl樹tree、node左邊的結點以及它右邊的結點(左右邊結點按mmap_avl中各vma->vma_end大小排序)。avl_ neighbours的功能是根據查找條件node在目標mmap_avl樹ptree中找到node左邊的結點以及它右邊的結點,並返回。
avl_remove(struct vm_area_struct * node_to_delete, ** ptree):輸入參數爲須要刪除的結點node_to_delete和目標mmap_avl樹ptree。avl_remove的功能是在目標mmap_avl樹ptree中找到結點node_to_delete並把它從平衡樹中刪除,而且調用avl_rebalance以保持ptree的平衡樹特性。
remove_shared_vm_struct(struct vm_area_struct *mpnt):輸入參數爲須要從inode->immap環中刪除的vma結點mpnt。remove_shared_vm_struct的功能是從擁有vma結點mpnt 的inode->immap環中刪除的該結點。

 

[目錄]


文件系統

 

[目錄]


inode


    學過操做系統的人對inode必定印象特別深入,這裏是我收集的細節
    一個inode有128 byte。在新建文件系統時, 一般會有一個參數, 用
來描述要分配多少比例的空間給inode table。舉例來講newfs -i 2048
是指文件系統中, 每分配2048 byte給data area, 就分配一個inode。
但一個inode並不必定用掉2048 byte, 也不是說 files allocation的最
小單位是2048 byte, 它僅僅只是表明文件系統中inode table/data area
分配空間的比例是 128/2048 也就是 1/16。若是 inode table 過小, 那
麼在每一個文件都很小的時候, 就會發生inode用光而存儲空間多餘的狀況。
file allocation的最小單位和inode多少沒有關係

[目錄]


proc


PROC文件系統是一個僞文件系統,它的文件和目錄是由Linux 操做系統核心提供的,以文件系統的方式爲訪問系統內核數據的操做提供接口,它們不佔用磁盤上的任何空間,有了這些文件和目錄, 用戶能夠更容易的瞭解操做系統核心或各個進程的狀態,並能對系統的一些參數進行配置。好比,一個系統內能打開的文件數最大缺省是1024,即系統最多能同時打開1024個文件,這在使用Linux作多用戶的服務器時是不夠用的,經過對/PROC下文件的修改能夠在不修改核心,甚至不啓動機器的狀況下改變這個缺省值。因爲系統的信息,如進程,是動態改變的,因此用戶或應用程序讀取PROC文件時,PROC文件系統是動態從系統內核讀出所需信息並提交的。它的目錄結構以下:

目錄名稱        目錄內容
apm         Advanced power management info
Cmdline         Kernel command line
Cpuinfo        Info about the CPU
Devices        Available devices (block and character)
Dma        Used DMS channels
Filesystems        Supported filesystems
Interrupts        Interrupt usage
Ioports        I/O port usage
Kcore        Kernel core image
Kmsg        Kernel messages
Ksyms        Kernel symbol table
Loadavg        Load average
Locks        Kernel locks
Meminfo        Memory info
Misc        Miscellaneous
Modules        List of loaded modules
Mounts        Mounted filesystems
Partitions        Table of partitions known to the system
Rtc        Real time clock
Slabinfo        Slab pool info
Stat        Overall statistics
Swaps        Swap space utilization
Version        Kernel version
Uptime        System uptime

並非全部這些目錄在你的系統中都有,這取決於你的內核配置和裝載的模塊。另外,在/proc下還有三個很重要的目錄:net,scsi和sys。Sys目錄是可寫的,能夠經過它來訪問或修改內核的參數,而net和scsi則依賴於內核配置。例如,若是系統不支持scsi,則scsi目錄不存在。
除了以上介紹的這些,還有的是一些以數字命名的目錄,它們是進程目錄。系統中當前運行的每個進程都有對應的一個目錄在/PROC下,以進程的PID號爲目錄名,它們是讀取進程信息的接口。而self目錄則是讀取進程自己的信息接口,是一個link。Proc文件系統的名字就是由之而起。進程目錄的結構以下:

目錄名稱        目錄內容
Cmdline        Command line arguments
Environ        Values of environment variables
Fd        Directory, which contains all file descriptors
Mem        Memory held by this process
Stat        Process status
Status        Process status in human readable form
Cwd        Link to the current working directory
Exe        Link to the executable of this process
Maps        Memory maps
Statm        Process memory status information
Root        Link to the root directory of this process

用戶若是要查看系統信息,能夠用cat命令。例如:
> cat /proc/interrupts
           CPU0
  0:    8728810          XT-PIC  timer
  1:        895          XT-PIC  keyboard
  2:          0          XT-PIC  cascade
  3:     531695          XT-PIC  aha152x
  4:    2014133          XT-PIC  serial
  5:      44401          XT-PIC  pcnet_cs
  8:          2          XT-PIC  rtc
11:          8          XT-PIC  i82365
12:     182918          XT-PIC  PS/2 Mouse
13:          1          XT-PIC  fpu
14:    1232265          XT-PIC  ide0
15:          7          XT-PIC  ide1
NMI:          0
2.修改內核參數
在/proc文件系統中有一個有趣的目錄:/proc/sys。它不只提供了內核信息,並且能夠經過它修改內核參數,來優化你的系統。可是你必須很當心,由於可能會形成系統崩潰。最好是先找一臺可有可無的機子,調試成功後再應用到你的系統上。
要改變內核的參數,只要用vi編輯或echo參數重定向到文件中便可。下面有一個例子:
# cat /proc/sys/fs/file-max
4096
# echo 8192 > /proc/sys/fs/file-max
# cat /proc/sys/fs/file-max
8192
若是你優化了參數,則能夠把它們寫成腳本文件,使它在系統啓動時自動完成修改。

PROC文件系統的初始化過程概述

PROC文件系統總的初始化流程以下圖所示,是從INIT/MAIN.C的START_KERNEL()函數中開始的,首先是PROC_ROOT_INIT(),在這個函數中用PROC_DIR_EMTRY註冊了/PROC及其目錄下的各個文件及目錄的基本信息,包括註冊INODE NUMBER,文件名長度,文件名,操做權限,鏈接數,用戶標識號,組標識號,容許的INODE OPERATIONS和兄弟、父母、子文件的指針等,並生成文件目錄之間的相互關係,即目錄樹。接下來在SYSCLT_INIT()裏把ROOT_TABLE的各項內容掛到/PROC樹的PROC_SYS_ROOT下面,PROC_SYS_ROOT是一個特殊的PROC項,即/PROC/SYS目錄,在此目錄下的部分文件是可寫的,能夠經過修改這些文件內容來修改系統配置參數。而後是FILESYSTEM_SETUP(),在這裏產生一個新的FILE_SYSTEM_TYPE:PROC_FS_TYPE,把這個文件系統類型掛到FILE_SYSTEMS鏈表的末尾,其中包含了讀取PROC文件系統SUPER BLOCK的函數指針,PROC_READ_SUPER,接下來在裝載了ROOT文件系統後,若是還要把PROC文件系統MOUNT上來則需經過此函數把PROC文件系統的SUPER BLOCK讀進內存裏的SUPER_BLOCKS鏈表。從上面能夠看到,PROC文件系統的初始化流程主要分兩步,即登記註冊和掛載文件系統,其中的核心內容是在登記註冊裏,下面就具體分析一下這兩部分的具體初始化操做。
3、PROC文件系統的登記註冊過程
從程序中來看,PROC文件系統的文件能夠分爲兩大類,一類是ROOT部分,另外一類是BASE部分,體如今初始化程序中分別叫作PROC_ROOT_INIT()和PROC_BASE_INIT()。ROOT部分主要是針對/PROC目錄下的一些名稱位置相對固定的常規性文件,如 CPUINFO,MEMINFO,KMESG,MODULES,DEVICES,INTERRUPTS,FILESYSTEMS,PARTITIONS,DMA,IOPORTS,CMDLINE,MOUNTS等;而BASE部分則是針對系統中每一個進程的,每一個運行中的進程在/PROC下都有一個以本身的進程號爲名字的目錄,裏面記載了關於此進程運行狀態的信息,如STATUS,MEM,CWD,ROOT,FD,EXE,CMDLINE,STAT,STATM等。下面將會分別介紹這兩部分的初始化過程,首先介紹一下基本的數據結構。
1.基本數據結構
在PROC        文件系統中最重要的數據結構是一個叫PROC_DIR_ENTRY的結構類型(include/linux/proc_fs.h),全部該文件系中的文件及目錄都除了通用文件操做用的INODE,FILE,DENTRY等結構外,都必須首先註冊本身的一個PROC_DIR_ENTRY,在其中定義了針對本身的各項屬性和操做函數指針。
struct proc_dir_entry {
unsigned short low_ino;          /*註冊inode號,實際inode的低16位*/
                unsigned short namelen;                   /*名稱字符串的長度*/
        const char *name;                        /*文件名稱*/
                mode_t mode;                           /*文件權限*/
        nlink_t nlink;                               /*鏈接到此文件的目錄項個數*/
                uid_t uid;                                   /*用戶標識*/
        gid_t gid;                               /*組標識*/
                unsigned long size;                       /*文件大小,均爲0*/
        struct inode_operations * ops;              /*inode操做的函數指針*/
                int (*get_info)(char *, char **, off_t, int, int);
        void (*fill_inode)(struct inode *, int);         /*填補inode信息的函數指針*/
        struct proc_dir_entry *next, *parent, *subdir;    /*兄弟,父親,子文件指針*/
void *data;
        int (*read_proc)(char *page, char **start, off_t off, int count,
int *eof, void *data);
        int (*write_proc)(struct file *file, const char *buffer,
                                  unsigned long count, void *data);
        int (*readlink_proc)(struct proc_dir_entry *de, char *page);
        unsigned int count;                                /*使用數*/
                int deleted;                                            /*刪除標誌*/
};
其次就是針對INODE,FILE和SUPER BLOCK的各類操做函數指針,這些結構與文件系統中使用的徹底相同,此處再也不贅述。
2.PROC_ROOT_INIT()
PROC_ROOT_INIT()函數的定義以下:
__initfunc(void proc_root_init(void))
{
        proc_base_init();
        proc_register(&proc_root, &proc_root_loadavg);
        proc_register(&proc_root, &proc_root_uptime);
        proc_register(&proc_root, &proc_root_meminfo);
        proc_register(&proc_root, &proc_root_kmsg);
        proc_register(&proc_root, &proc_root_version);
        proc_register(&proc_root, &proc_root_cpuinfo);
        proc_register(&proc_root, &proc_root_self);
#ifdef CONFIG_SYSCTL
        proc_register(&proc_root, &proc_sys_root);
#endif
#ifdef CONFIG_MCA
        proc_register(&proc_root, &proc_mca);
#endif

#ifdef CONFIG_DEBUG_MALLOC
        proc_register(&proc_root, &proc_root_malloc);
#endif
#ifdef CONFIG_MODULES
        proc_register(&proc_root, &proc_root_modules);
        proc_register(&proc_root, &proc_root_ksyms);
#endif
        proc_register(&proc_root, &proc_root_stat);
        proc_register(&proc_root, &proc_root_devices);
        proc_register(&proc_root, &proc_root_partitions);
        proc_register(&proc_root, &proc_root_interrupts);
        proc_register(&proc_root, &proc_root_filesystems);
        proc_register(&proc_root, &proc_root_fs);
        proc_register(&proc_root, &proc_root_dma);
        proc_register(&proc_root, &proc_root_ioports);
        proc_register(&proc_root, &proc_root_cmdline);
                ……
                }
__initfunc是在inlucde/linux/init.h中定義的一個宏,表示此函數僅在系統初始化時使用,使用完畢即釋放申請的內存資源。
PROC_REGISTER()函數在fs/proc/root.c中定義,程序以下:
int proc_register(struct proc_dir_entry * dir, struct proc_dir_entry * dp)
{
        int        i;
        if (dp->low_ino == 0) {
                i = make_inode_number();
                if (i < 0)
                        return -EAGAIN;
                dp->low_ino = i;
        }
/*若是沒有low_ino值,則產生一個新的賦給它*/
        dp->next = dir->subdir;
        dp->parent = dir;
        dir->subdir = dp;
/*賦給兄弟、父母及子女的指針*/
        if (S_ISDIR(dp->mode)) {
                if (dp->ops == NULL)
                        dp->ops = &proc_dir_inode_operations;
                dir->nlink++;
        } else if (S_ISLNK(dp->mode)) {
                if (dp->ops == NULL)
                        dp->ops = &proc_link_inode_operations;
        } else {
                if (dp->ops == NULL)
                        dp->ops = &proc_file_inode_operations;
        }
/*對於dp的不一樣屬性調整操做函數指針*/
        return 0;
}
初始化時首先要爲每一個文件或目錄建立一個PROC_DIR_ENTRY的實例變量,內容包括註冊的INODE號,名稱,操做權限,鏈接數和INODE操做函數指針等,具體方法以下所示:
struct proc_dir_entry proc_root = {
        PROC_ROOT_INO, 5, "/proc", S_IFDIR | S_IRUGO | S_IXUGO,
  2, 0, 0, 0, &proc_root_inode_operations, NULL, NULL, NULL,
        &proc_root, NULL };
struct proc_dir_entry proc_mca = {
        PROC_MCA, 3, "mca", S_IFDIR | S_IRUGO | S_IXUGO,
  2, 0, 0, 0, &proc_dir_inode_operations, NULL, NULL, NULL,
  &proc_root, NULL };
static struct proc_dir_entry proc_root_loadavg = {
        PROC_LOADAVG, 7, "loadavg",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations };
static struct proc_dir_entry proc_root_uptime = {
        PROC_UPTIME, 6, "uptime",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations };
static struct proc_dir_entry proc_root_meminfo = {
        PROC_MEMINFO, 7, "meminfo",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations };
static struct proc_dir_entry proc_root_kmsg = {
        PROC_KMSG, 4, "kmsg", S_IFREG | S_IRUSR, 1, 0, 0,
        0, &proc_kmsg_inode_operations };
......
PROC_REGISTER()函數首先檢查這個新的PROC_DIR_ENTRY是否已有本身的PROC INODE號,如前所述,PROC文件系統是個「僞」文件系統,它並不存在於任何實際的物理設備上,因此它的文件INODE號與普通文件的INODE號的涵義是不一樣的,它不須要標識此文件的物理存在位置,只要能在本文件系統中惟一地標識這個文件便可。對於ROOT部分的文件在include/proc_fs.h中用一個枚舉變量enum root_directory_inos來對其賦值,其中第一個文件即/PROC目錄用PROC_ROOT_INO=1來定義。若是此文件在變量初始化時未賦值,LINUX用一個proc_alloc_map來表示INODE的使用狀況,proc_alloc_map是一塊有4096個bits的內存空間,每一位表示一個INODE的使用狀況,如已經被使用則置位,放棄時恢復。分配時找其中第一位未使用的bit,在其位數上加上4096就是這個新登記文件的PROC INODE號。在此以後,PROC_REGISTER()調整父目錄與子目錄/文件之間的指針關係,及子目錄/文件的INODE操做函數指針,最終結果是生成一棵以PROC_DIR_ENTRY爲節點,PROC_ROOT爲根的目錄樹,代表其相互之間的關係,在之後對文件或目錄作標準操做(即用INODE,FILE,DENTRY等結構)的時候就能夠從中得到須要的信息和函數指針。

 

在解釋過PROC_REGISTER()函數後順便再說明一下它的反向操做函數PROC_UNREGISTER(),即註銷一個PROC_DIR_ENTRY項,其定義以下:

int proc_unregister(struct proc_dir_entry * dir, int ino)
{
        struct proc_dir_entry **p = &dir->subdir, *dp;
/*從dir的subdir指針開始進行搜索*/
        while ((dp = *p) != NULL) {          /*在dp指針尚不爲空時*/
                if (dp->low_ino == ino) {    /*若是low_ino==ino,說明僅對root部分*/
                        *p = dp->next;
                        dp->next = NULL;
         /*兄弟指針置空*/
                        if (S_ISDIR(dp->mode))
                                dir->nlink--;
        /*若是dp是目錄,其父目錄的鏈接數要減去1*/
                        if (ino >= PROC_DYNAMIC_FIRST &&
                            ino < PROC_DYNAMIC_FIRST+PROC_NDYNAMIC)
                                clear_bit(ino-PROC_DYNAMIC_FIRST,
                                          (void *) proc_alloc_map);
        /*若是是在程序中生成的low_ino,則要清除對應的proc_allc_map中的*/
         /*位標誌*/
                        proc_kill_inodes(ino);
                        return 0;
                }
                p = &dp->next;            /*p指向dp的兄弟指針,繼續搜索*/
        }
        return -EINVAL;
}
在PROC_UNREGISTER()中調用了另外一個函數PROC_KILL_INODE(),該函數的功能是把一個已經被註銷的PROC文件系統的INODE消滅掉,定義以下:
static void proc_kill_inodes(int ino)
{
        struct file *filp;

        /* inuse_filps is protected by the single kernel lock */
        for (filp = inuse_filps; filp; filp = filp->f_next) {
     /*在正在使用中的文件鏈表中進行搜索*/
                struct dentry * dentry;
                struct inode * inode;

                dentry = filp->f_dentry;
                if (!dentry)
                        continue;
                if (dentry->d_op != &proc_dentry_operations)
                        continue;
     /*若是該項不屬於PROC文件系統,則繼續*/
                inode = dentry->d_inode;
                if (!inode)
                        continue;
                if (inode->i_ino != ino)
                        continue;
                filp->f_op = NULL;
     /*把該文件的操做函數指針置空,之後就沒法使用了*/
        }
}
3.PROC_BASE_INIT()
PROC_BASE_INIT()函數在PROC_ROOT_INIT()中調用,BASE部分的初始化與ROOT部分基本相同,首先爲每一個目錄和文件初始化一個PROC_DIR_ENTRY結構,而後調用PROC_REGISTER()函數進行註冊,並生成BASE部分的關係樹,程序以下:
struct proc_dir_entry proc_pid = {
        PROC_PID_INO, 5, "<pid>",        S_IFDIR | S_IRUGO | S_IXUGO, 2, 0, 0,
        0, &proc_base_inode_operations,        NULL, proc_pid_fill_inode,
        NULL, &proc_root, NULL
};

static struct proc_dir_entry proc_pid_status = {
        PROC_PID_STATUS, 6, "status",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_mem = {
        PROC_PID_MEM, 3, "mem",        S_IFREG | S_IRUSR | S_IWUSR, 1, 0, 0,
        0, &proc_mem_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_cwd = {
        PROC_PID_CWD, 3, "cwd",        S_IFLNK | S_IRWXU, 1, 0, 0,
        0, &proc_link_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_root = {
        PROC_PID_ROOT, 4, "root",        S_IFLNK | S_IRWXU, 1, 0, 0,
        0, &proc_link_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_exe = {
        PROC_PID_EXE, 3, "exe",        S_IFLNK | S_IRWXU, 1, 0, 0,
        0, &proc_link_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_fd = {
        PROC_PID_FD, 2, "fd",        S_IFDIR | S_IRUSR | S_IXUSR, 2, 0, 0,
        0, &proc_fd_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_environ = {
        PROC_PID_ENVIRON, 7, "environ",        S_IFREG | S_IRUSR, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_cmdline = {
        PROC_PID_CMDLINE, 7, "cmdline",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_stat = {
        PROC_PID_STAT, 4, "stat",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_statm = {
        PROC_PID_STATM, 5, "statm",        S_IFREG | S_IRUGO, 1, 0, 0,
        0, &proc_array_inode_operations,        NULL, proc_pid_fill_inode,
};
static struct proc_dir_entry proc_pid_maps = {
        PROC_PID_MAPS, 4, "maps",        S_IFIFO | S_IRUGO, 1, 0, 0,
        0, &proc_arraylong_inode_operations,        NULL, proc_pid_fill_inode, };


__initfunc(void proc_base_init(void))
{
#if CONFIG_AP1000
        proc_register(&proc_pid, &proc_pid_ringbuf);
#endif
        proc_register(&proc_pid, &proc_pid_status);
        proc_register(&proc_pid, &proc_pid_mem);
        proc_register(&proc_pid, &proc_pid_cwd);
        proc_register(&proc_pid, &proc_pid_root);
        proc_register(&proc_pid, &proc_pid_exe);
        proc_register(&proc_pid, &proc_pid_fd);
        proc_register(&proc_pid, &proc_pid_environ);
        proc_register(&proc_pid, &proc_pid_cmdline);
        proc_regis
ter(&proc_pid, &proc_pid_stat);
        proc_register(&proc_pid, &proc_pid_statm);
        proc_register(&proc_pid, &proc_pid_maps);
#ifdef __SMP__
        proc_register(&proc_pid, &proc_pid_cpu);
#endif
};
這部分與ROOT部分的不一樣之處一是BASE部分的文件/目錄的PROC INODE 號是用另外一個枚舉變量enum pid_directory_inos來賦值,其第一項PROC_PID_INO=2,即<pid>目錄。因爲ROOT部分的每一個文件/目錄在/PROC下只有惟一的一個實例,而BASE部分對每一個進程均有相同的一份拷貝,因此它的實際INODE號必須對不一樣的進程予以區分,在LINUX中,這種區分是經過把進程PID作爲INODE的高16位,PROC_DIR_ENTRY中的LOW_INO作爲INODE的低16位來實現的,這樣能夠保證每一個文件INODE號在文件系統中的惟一性。另外一個不一樣之處是BASE部分的文件在註冊其PROC_DIR_ENTRY的時候都增長了FILL_INODE函數指針,指向PROC_PID_FILL_INODE函數,該函數的主要功能是把實際INODE號右移16位,得到對應此目錄或文件的進程PID號,再把此進程的用戶標識和組標識賦回給INODE結構。還有一點區別就是PROC_BASE_INIT()初始化的這棵PROC_DIR_ENTRY樹是相對獨立的(如圖2),它以<pid>目錄,即PROC_PID項爲根,並註冊了該目錄下各個文件及目錄之間的相互關係,但它並不把PROC_PID掛到PROC_ROOT下面去,由於這是要在對PROC_ROOT作READDIR時動態加載的。
4.INODE OPERATIONS
有一點須要強調的是PROC文件系統在初始化時只是爲每一個目錄和文件登記了一個PROC_DIR_ENTRY項,併爲這些項生成了關係樹,但對於VFS的通用文件操做作使用的數據結構,如INODE,FILE,DENTRY等此時都是不存在的,這些數據結構要等到在對PROC文件系統的文件作OPEN和READ等操做時纔會根據PROC_DIR_ENTRY裏定義的INODE OPERATION及其中的FILE OPERATIONS函數指針調用對應函數產生,這是PROC文件系統與其餘基於設備的文件系統的一大區別之處。因此,在PROC_DIR_ENTRY中定義的INODE OPERATIONS決定了該文件的操做方式以及獲取對應系統信息的渠道。舉例來講,PROC_ROOT指向的PROC_ROOT_INODE_OPERATIONS中容許的INODE OPERATIONS函數是PROC_ROOT_LOOKUP,FILE OPERATIONS函數是PROC_ROOT_READDIR ;PROC_PID指向的PROC_BASE_INODE_OPERATIONS中容許的INODE OPERATIONS函數是PROC_LOOKUP,FILE OPERATIONS函數是PROC_ READDIR ;PROC_MEM_INFO指向的PROC_ARRAY_INODE_OPERATIONS中容許的INODE OPERATIONS函數均爲空,FILE OPERATIONS函數是ARRAY_READ。下面咱們來分析一下幾個LOOKUP和READDIR函數,ARRAY_READ的功能主要是面向底層如何獲取系統信息反饋上來,詳見黃軍同窗的報告,這裏就不作詳述了。
PROC_LOOKUP、PROC_READDIR與PROC_ROOT_LOOKUP、PROC_ROOT_READDIR的功能基本上是相同的,只不過加上了ROOT後就加上了對<PID>目錄的處理功能。
程序以下所示:
1) proc_lookup()
int proc_lookup(struct inode * dir, struct dentry *dentry)
{
        struct inode *inode;
        struct proc_dir_entry * de;
        int error;

        error = -ENOTDIR;
        if (!dir || !S_ISDIR(dir->i_mode))
                goto out;
/*若是dir空或dir不是目錄,則退出*/
        error = -ENOENT;
        inode = NULL;
        de = (struct proc_dir_entry *) dir->u.generic_ip;
/*根據dir生成一個proc_dir_entry的指針*/
        if (de) {
                for (de = de->subdir; de ; de = de->next) {
         /*在de的子目錄和文件中搜索*/
                        if (!de || !de->low_ino)
                                continue;
                        if (de->namelen != dentry->d_name.len)
                                continue;
                        if (!memcmp(dentry->d_name.name, de->name, de->namelen)) {
            /*若是dentry和由dir指向的proc_dir_entry名字相同*/
                                int ino = de->low_ino | (dir->i_ino & ~(0xffff));
                                error = -EINVAL;
                                inode = proc_get_inode(dir->i_sb, ino, de);
                 /*申請一個inode節點,對應的proc_dir_entry節點爲de,節點號*/
                 /*爲ino。同時把de的數據填入inode */
                                break;
                        }
                }
        }

        if (inode) {
                dentry->d_op = &proc_dentry_operations;
                d_add(dentry, inode);
    /*把dentry放到dentry_hash_table表中而後把inode的I_dentry和dentry的*/
    /*d_alias相連*/
    error = 0;
        }
out:
        return error;
}

   2) proc_root_lookup()
static int proc_root_lookup(struct inode * dir, struct dentry * dentry)
{
        unsigned int pid, c;
        struct task_struct *p;
        const char *name;
        struct inode *inode;
        int len;

        if (dir->i_ino == PROC_ROOT_INO) { /* check for safety... */
                dir->i_nlink = proc_root.nlink;

                read_lock(&tasklist_lock);  /*加讀進程列表的鎖*/
                for_each_task(p) {
                        if (p->pid)
                                dir->i_nlink++; /*對於每一個進程都要把proc_root的link數加1*/
                }
                read_unlock(&tasklist_lock);  /*解讀進程列表的鎖*/
        }

        if (!proc_lookup(dir, dentry)) /*若是調用proc_lookup成功則返回*/
                return 0;
        /*若是調用proc_lookup不成功,說明要找的是pid部分的*/
        pid = 0;
        name = dentry->d_name.name;
        len = dentry->d_name.len;
        while (len-- > 0) {
                c = *name - '0';
                name++;
                if (c > 9) {
                        pid = 0;
                        break;
                }
                pid *= 10;
                pid += c;
 /*把目錄名的字符串轉換成整數型的進程號*/
                if (pid & 0xffff0000) {
                        pid = 0;
                        break;
                }
        }
        read_lock(&tasklist_lock);
        p = find_task_by_pid(pid);
        read_unlock(&tasklist_lock);
        inode = NULL;
        if (pid && p) {
                unsigned long ino = (pid << 16) + PROC_PID_INO;
                inode = proc_get_inode(dir->i_sb, ino, &proc_pid);
                if (!inode)
                        return -EINVAL;
                inode->i_flags|=S_IMMUTABLE;
        }

        dentry->d_op = &proc_dentry_operations;
        d_add(dentry, inode);
        return 0;
}

3) proc_readdir()
int proc_readdir(struct file * filp, void * dirent, filldir_t filldir)
{
        struct proc_dir_entry * de;
        unsigned int ino;
        int i;
        struct inode *inode = filp->f_dentry->d_inode;

        if (!inode || !S_ISDIR(inode->i_mode))
                return -ENOTDIR;
        ino = inode->i_ino;
        de = (struct proc_dir_entry *) inode->u.generic_ip;
        if (!de)
                return -EINVAL;
        i = filp->f_pos;
        switch (i) {
                case 0:
                        if (filldir(dirent, ".", 1, i, ino) < 0)
                                return 0;
                        i++;
                        filp->f_pos++;
                        /* fall through */
                case 1:
                        if (filldir(dirent, "..", 2, i, de->parent->low_ino) < 0)
                                return 0;
                        i++;
                        filp->f_pos++;
                        /* fall through */
                default:
                        ino &= ~0xffff;
                        de = de->subdir;
                        i -= 2;
                        for (;;) {
                                if (!de)
                                        return 1;
                                if (!i)
                                        break;
                                de = de->next;
                                i--;
                        }

                        do {
                                if (filldir(dirent, de->name, de->namelen, filp->f_pos, ino | de->low_ino) < 0)
                                        return 0;
                                filp->f_pos++;
                                de = de->next;
                        } while (de);
        }
        return 1;
}


4) get_pid_list()
static int get_pid_list(int index, unsigned int *pids)
{
        struct task_struct *p;
        int nr_pids = 0;

        index -= FIRST_PROCESS_ENTRY;
        read_lock(&tasklist_lock);
        for_each_task(p) {
                int pid = p->pid;
                if (!pid)
                        continue;
                if (--index >= 0)
                        continue;
                pids[nr_pids] = pid;
                nr_pids++;
                if (nr_pids >= PROC_MAXPIDS)
                        break;
        }
        read_unlock(&tasklist_lock);
        return nr_pids;
}


5) proc_root_readdir()
static int proc_root_readdir(struct file * filp,        void * dirent, filldir_t filldir)
{
        unsigned int pid_array[PROC_MAXPIDS];
        char buf[PROC_NUMBUF];
        unsigned int nr = filp->f_pos;
        unsigned int nr_pids, i;

        if (nr < FIRST_PROCESS_ENTRY) {
                int error = proc_readdir(filp, dirent, filldir);
                if (error <= 0)
                        return error;
                filp->f_pos = nr = FIRST_PROCESS_ENTRY;
        }

        nr_pids = get_pid_list(nr, pid_array);

        for (i = 0; i < nr_pids; i++) {
                int pid = pid_array[i];
                ino_t ino = (pid << 16) + PROC_PID_INO;
                unsigned long j = PROC_NUMBUF;

                do {
                        j--;
                        buf[j] = '0' + (pid % 10);
                        pid /= 10;
                } while (pid);

                if (filldir(dirent, buf+j, PROC_NUMBUF-j, filp->f_pos, ino) < 0)
                        break;
                filp->f_pos++;
        }
        return 0;
}


在這裏經過inode_operation和file_operation註冊的這些函數是在對/PROC下的文件及目錄進行open及read操做時才被調用,生成對應的inode,file,dentry等數據結構並取得對應的系統信息反饋

PROC文件系統的掛載過程
FILESYSTEM_SETUP()主要就是調用了一系列的INIT_*_FS()函數(*表明各類不一樣的文件系統),對PROC文件系統就是INIT_PROC_FS(),在此函數中實例化了一個FILE_SYSTEM_TYPE類型的結構變量PROC_FS_TYPE,其中包括此文件系統的名字和PROC_READ_SUPER函數指針,而後經過REGISTER_FILESYSTEM函數把它掛到FILE_SYSTEMS鏈表的末尾。
/*  fs/filesystems.c  */
void __init filesystem_setup(void)
{
#ifdef CONFIG_EXT2_FS
        init_ext2_fs();     /*  初始化ext2文件系統  */
#endif
......
#ifdef CONFIG_PROC_FS
        init_proc_fs();     /*  初始化proc文件系統  */
#endif
......
} ;

/*  fs/proc/procfs_syms.c  */
static struct file_system_type proc_fs_type = {
        "proc",
        0 /* FS_NO_DCACHE doesn't work correctly */,
        proc_read_super,    /*針對本文件系統讀取super_block的函數指針*/
        NULL } ;
int init_proc_fs(void)
{
        return register_filesystem(&proc_fs_type) == 0;
}

/*   fs/super.c   */
int register_filesystem(struct file_system_type * fs)
{
        struct file_system_type ** tmp;

        if (!fs)
                return -EINVAL;
        if (fs->next)
                return -EBUSY;
        tmp = &file_systems;
        while (*tmp) {
                if (strcmp((*tmp)->name, fs->name) == 0)
                        return -EBUSY;
                tmp = &(*tmp)->next;
        } /*遍歷file_systems鏈表*/
        *tmp = fs;  /*把fs掛到file_systems鏈表末尾*/
        return 0;
}

在系統啓動時若是須要把PROC文件系統掛載上來則要根據PROC_FS_TYPE中註冊的PROC_READ_SUPER()函數把SUPER BLOCK讀入內存,並加入SUPER_BLOCKS鏈表。對於通常基於硬設備上的文件系統,READ_SUPER函數的操做就是在內存中建立一個新的空SUPER BLOCK,而後從設備上把SUPER BLOCK讀入。但對於PROC文件系統,其READ_SUPER操做的內容有所不一樣,由於PROC文件系統是不存在於任何設備上的,因此PROC_READ_SUPER函數就是在產生一個新的空SUPER_BLOCK以後在內存中鎖住該塊後直接對其進行修改了。
/*   fs/proc/inode.c   */
struct super_block *proc_read_super(struct super_block *s,void *data, int silent)
{
        struct inode * root_inode;

        lock_super(s);    /*鎖住該super_block*/
        s->s_blocksize = 1024;
        s->s_blocksize_bits = 10;
        s->s_magic = PROC_SUPER_MAGIC;
        s->s_op = &proc_sops;
        root_inode = proc_get_inode(s, PROC_ROOT_INO, &proc_root);
        if (!root_inode)
                goto out_no_root;
        s->s_root = d_alloc_root(root_inode, NULL);
        if (!s->s_root)
                goto out_no_root;
        parse_options(data, &root_inode->i_uid, &root_inode->i_gid);
        unlock_super(s);
        return s;

out_no_root:
        printk("proc_read_super: get root inode failed/n");
        iput(root_inode);
        s->s_dev = 0;
        unlock_super(s);
        return NULL;
}

[目錄]


CD_ROM


對CD_ROM的訪問操做


分析ide_cd.c下的各個函數的功能,以及和其餘函數之間的調用關係。首先是對CD_ROM的讀寫數據。函數cdrom_in_bytes()是讀數據,函數cdrom_out_bytes()是寫數據。實現緩衝區與CD_ROM之間的數據交換。這兩個函數分別調用了input_ide_data()和output_ide_data()函數。input_ide_data()是通用的從IDE設備讀數據的函數,output_ide_data()是通用的往IDE設備寫數據的函數。即不只是訪問IDE CD_ROM用到這兩個函數,並且訪問其餘IDE設備如軟驅,也用到這兩個函數。這兩個函數提供了底層的訪問IDE設備的功能。
咱們知道了用於緩衝區與CD_ROM之間直接進行數據交換的函數,同時在請求管理部分中講到,文件系統對塊設備的訪問並不是是直接對設備進行的,而是經過緩衝區實現的。因而下面討論文件系統訪問CD_ROM上的文件時,各個層次間函數的調用關係。
首先在前面提到的系統核心初始化過程當中,函數hwif_init()爲CD_ROM分配了中斷號,同時也指定了請求響應的列程。hwif_init()中經過如下語句實現。
//爲CD_ROM分配了中斷號
                if (!(hwif->irq = default_irqs[h])) {
                        printk("%s: DISABLED, NO IRQ/n", hwif->name);
                        return (hwif->present = 0);
//指定了請求響應的列程
        switch (hwif->major) {
        case IDE0_MAJOR: rfn = &do_ide0_request; break;
#if MAX_HWIFS > 1
        case IDE1_MAJOR: rfn = &do_ide1_request; break;
#endif
#if MAX_HWIFS > 2
        case IDE2_MAJOR: rfn = &do_ide2_request; break;
#endif
#if MAX_HWIFS > 3
        case IDE3_MAJOR: rfn = &do_ide3_request; break;
#endif
        default:
                printk("%s: request_fn NOT DEFINED/n", hwif->name);
                return (hwif->present = 0);
        }                blk_dev[hwif->major].request_fn = rfn;
…….

跟蹤函數do_ide*_request(),不難發現對CD_ROM請求的具體實現。各函數之間的關係如圖4。下面介紹主要函數的意義:
do_hwgroup_request()//contained in drivers/block/ide.c
do_hwgroup_request()首先屏蔽該設備全部可能發生的中斷,避免競爭,而後調用ide_do_request() 。
ide_do_request() //contained in drivers/block/ide.c
函數首先用cli()屏蔽中斷標誌位。而後調用do_request()。

do_request() //contained in drivers/block/ide.c
do_request()對新的I/O請求作初始化。Do_request()帶參數ide_hwif_t *hwif,根據&hwif->drives[unit]->media的不一樣,進入特定的請求處理例程,當&hwif->drives[unit]->media爲ide_cdrom時,進入CD_ROM請求例程ide_do_rw_cdrom。
ide_do_rw_cdrom() //contained in drivers/block/ide_cd.c
本函數定義以下:
void ide_do_rw_cdrom (ide_drive_t *drive, unsigned long block)
{
        struct request *rq = HWGROUP(drive)->rq;

        if (rq -> cmd == PACKET_COMMAND || rq -> cmd == REQUEST_SENSE_COMMAND)
                cdrom_do_packet_command (drive);
        else if (rq -> cmd == RESET_DRIVE_COMMAND) {
                cdrom_end_request (1, drive);
                ide_do_reset (drive);
                return;
        } else if (rq -> cmd != READ) {
                printk ("ide-cd: bad cmd %d/n", rq -> cmd);
                cdrom_end_request (0, drive);
        } else
                cdrom_start_read (drive, block);
}
函數根據請求內容的不一樣,即rq->cmd的不一樣,執行相應的驅動函數。當rq->cmd爲PACKET_COMMAND或REQUEST_SENSE_COMMAND時,執行cdrom_do_packet_command();當rq->cmd爲READ時,執行cdrom_start_read()。在cdrom_do_packet_command()和cdrom_start_read ()中都激發了一個重要的函數:cdrom_transfer_packet_command(),該函數的參數定義以下:
        static int cdrom_transfer_packet_command (ide_drive_t *drive,
                                         char *cmd_buf, int cmd_len,
                                          ide_handler_t *handler)

該函數發一個包命令(packet command)給設備,設備在參數drive中註明,包命令用參數CMD_BUF和CMD_LEN表示。參數HANDLER是中斷句柄,當包命令完成時,HANDLER將被調用。在函數cdrom_start_read()中cdrom_transfer_packet_command()被調用的形式以下:
                (void) cdrom_transfer_packet_command (drive, pc.c, sizeof (pc.c),
                                              &cdrom_read_intr);
在函數cdrom_do_packet_command()中cdrom_transfer_packet_command()被調用的形式以下:
        cdrom_transfer_packet_command (drive, pc->c,
                                       sizeof (pc->c), &cdrom_pc_intr);
以cdrom_start_read()爲例,探討系統訪問塊設備所採起的策略。如下爲cdrom_start_read()的僞代碼:
/*contained in drivers/block/ide_cd.c
* Start a read request from the CD-ROM.
*/
static void cdrom_start_read (ide_drive_t *drive, unsigned int block)
{
//獲取drive的請求數據
        struct request *rq = HWGROUP(drive)->rq;
        int minor = MINOR (rq->rq_dev);
        若是請求是針對一個分區的,則使請求指向分區的絕對地址。
        /* We may be retrying this request after an error.  Fix up
           any weirdness which might be present in the request packet. */
        恢復可能被部分改變的請求結構rq,restore_request (rq);

        根據請求,在緩衝區內確認是否能知足,即先在緩衝區內查找請求所需的數據。若是請求被知足,則結束該請求cdrom_end_request (1, drive),返回;若是沒有被知足,則繼續。
        if (cdrom_read_from_buffer (drive))
                return;
        若是緩衝區沒法知足請求,則將讀請求送到設備,當請求完成後,執行相應的中斷例程
/* Start sending the read request to the drive. */
        cdrom_start_packet_command (drive, 32768,
                                    cdrom_start_read_continuation);
}


IDE_CD的打開和關閉

打開設備的操做是ide_cdrom_open(),函數的內容很簡單,若是是第一次打開,則檢測CD_ROM的狀態,不然不作任何事情。關閉設備的操做是ide_cdrom_release(),主要的操做是釋放與該設備有關的內存空間。
IDE_CD的ioctl操做
IDE CD_ROM的ioctl操做內容較多。經過函數ide_cdrom_ioctl()實現,主要有如下操做:
                                        CDROMEJECT                //彈出
                                        CDROMCLOSETRAY        //關閉托盤
                                        CDROMEJECT_SW
                                        CDROMPAUSE                //暫停
                                        CDROMRESUME
                                        CDROMSTART
                                        CDROMSTOP
                                        CDROMREADTOCHDR
                                        CDROMREADTOCENTRY
                                        CDROMVOLCTRL
                                        CDROMVOLREAD
                                        CDROMMULTISESSION
                                        CDROMREADRAW

 

[目錄]


文件頁緩衝結構


inode結構定義了操做數據文件的函數表i_fop,它是文件系統提供的面向用戶的高級文件IO接口.inode結構還定義了i_mapping指針,用它來描述對文件的IO緩衝.i_mapping->a_ops是文件系統提供的一組低級文件IO函數表,a_ops與塊設備接口.在一般狀況下,i_fop並不直接與塊設備接口,而是間接經過a_ops讀寫文件.文件的頁緩衝(page cache)就是i_fop與a_ops之間的緩衝,它是塊設備緩衝之上的更高一級緩衝,直接用於具體文件的讀寫.

頁緩衝用page_hash_table來索引不一樣文件不一樣位置上的頁塊.page_hash_table是頁面結構指針(struct page *)組成的數組,它的鍵由i_mapping地址和文件的頁塊號組成,i_mapping指向inode結構中的i_data結構,它的值對不一樣的inode是不一樣的,它的尺寸分配和dentry_hashtable的分配相似,每4M內存分配1個4K頁面,每一個頁面則1024項.

page_hash(mapping,index)
以mapping:index爲鍵返回page_hash_table中相應槽位的地址;

add_to_page_cache(page,mapping,index)
將page鏈入以mapping:index爲鍵值的page_hash_table中;

add_to_page_cache_unique(page,mapping,index,hash)
在槽位hash上惟一地添加以mapping:index表示的頁page;

__find_page_nolock(mapping,index,page)
在page_hash_table中查詢以mapping:index爲鍵的page;

remove_page_from_hash_queue(page)
將page從page_hash_table中刪除;

 

typedef struct page {
        struct list_head list;
        struct address_space *mapping; 爲inode->i_mapping的值
        unsigned long index; 文件的頁塊號
        struct page *next_hash; 用以造成hash鏈
        atomic_t count; 引用計數
        unsigned long flags;
        struct list_head lru;
        unsigned long age;
        wait_queue_head_t wait;
        struct page **pprev_hash; 指向hash鏈中前一頁next_hash的地址
        struct buffer_head * buffers;
        void *virtual;
        struct zone_struct *zone;
} mem_map_t;
struct address_space {
        struct list_head        clean_pages; 加入page_hash_table後,與page->list相環接
        struct list_head        dirty_pages;
        struct list_head        locked_pages;
        unsigned long                nrpages; 表示該inode具備的文件緩衝頁數量
        struct address_space_operations *a_ops;
        struct inode                *host;
        struct vm_area_struct        *i_mmap;
        struct vm_area_struct        *i_mmap_shared;
        spinlock_t                i_shared_lock;
};
struct address_space_operations {
        int (*writepage)(struct page *);
        int (*readpage)(struct file *, struct page *);
        int (*sync_page)(struct page *);
        int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
        int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
        int (*bmap)(struct address_space *, long);
};

#define page_hash(mapping,index) (page_hash_table+_page_hashfn(mapping,index))

atomic_t page_cache_size = ATOMIC_INIT(0);
unsigned int page_hash_bits;
struct page **page_hash_table;
extern inline unsigned long _page_hashfn(struct address_space * mapping, unsigned long index)
{
#define i (((unsigned long) mapping)/(sizeof(struct inode)  ~ (sizeof(struct inode) - 1)))
#define s(x) ((x)+((x)>>PAGE_HASH_BITS))
        return s(i+index)  (PAGE_HASH_SIZE-1);
#undef i
#undef s
}

void add_to_page_cache(struct page * page, struct address_space * mapping, unsigned long offset)
{
        spin_lock(
        __add_to_page_cache(page, mapping, offset, page_hash(mapping, offset));
        spin_unlock(
}
static inline void __add_to_page_cache(struct page * page,
        struct address_space *mapping, unsigned long offset,
        struct page **hash)
{
        unsigned long flags;

        if (PageLocked(page))
                BUG();

        flags = page->flags  ~((1         page->flags = flags | (1         page_cache_get(page); 增長引用計數
        page->index = offset;
        add_page_to_inode_queue(mapping, page); 將page->list與inode->i_mapping->clean_pages相環接
        add_page_to_hash_queue(page, hash); 將page加入hash槽位
        lru_cache_add(page); 與active_list相環接
}
static inline void add_page_to_inode_queue(struct address_space *mapping, struct page * page)
{
        struct list_head *head =

        mapping->nrpages++;
        list_add( head);
        page->mapping = mapping; 讓page->mapping指向inode->i_mapping
}
static void add_page_to_hash_queue(struct page * page, struct page **p)
{
        struct page *next = *p;

        *p = page;
        page->next_hash = next;
        page->pprev_hash = p;
        if (next)
                next->pprev_hash =
        if (page->buffers)
                PAGE_BUG(page);
        atomic_inc(
}
static inline struct page * __find_page_nolock(struct address_space *mapping, unsigned long offset, struct page *page)
{
        goto inside;

        for (;;) {
                page = page->next_hash;
inside:
                if (!page)
                        goto not_found;
                if (page->mapping != mapping)
                        continue;
                if (page->index == offset)
                        break;
        }
        /*
        * Touching the page may move it to the active list.
        * If we end up with too few inactive pages, we wake
        * up kswapd.
        */
        age_page_up(page);
        if (inactive_shortage() > inactive_target / 2  free_shortage())
                        wakeup_kswapd();
not_found:
        return page;
}
void add_to_page_cache_locked(struct page * page, struct address_space *mapping, unsigned long index)
{
        if (!PageLocked(page))
                BUG();

        page_cache_get(page);
        spin_lock(
        page->index = index;
        add_page_to_inode_queue(mapping, page);
        add_page_to_hash_queue(page, page_hash(mapping, index));
        lru_cache_add(page);
        spin_unlock(
}
void add_to_page_cache(struct page * page, struct address_space * mapping, unsigned long offset)
{
        spin_lock(
        __add_to_page_cache(page, mapping, offset, page_hash(mapping, offset));
        spin_unlock(
}


static int add_to_page_cache_unique(struct page * page,
        struct address_space *mapping, unsigned long offset,
        struct page **hash)
{
        int err;
        struct page *alias;

        spin_lock(
        alias = __find_page_nolock(mapping, offset, *hash);

        err = 1;
        if (!alias) {
                __add_to_page_cache(page,mapping,offset,hash);
                err = 0;
        }

        spin_unlock(
        return err;
}

static inline void remove_page_from_hash_queue(struct page * page)
{
        struct page *next = page->next_hash;
        struct page **pprev = page->pprev_hash;

        if (next)
                next->pprev_hash = pprev;
        *pprev = next;
        page->pprev_hash = NULL;
        atomic_dec(
}

static inline void remove_page_from_inode_queue(struct page * page)
{
        struct address_space * mapping = page->mapping;

        mapping->nrpages--;
        list_del(
        page->mapping = NULL;
}

void remove_inode_page(struct page *page)
{
        if (!PageLocked(page))
                PAGE_BUG(page);

        spin_lock(
        __remove_inode_page(page);
        spin_unlock(
}
/*
* Remove a page from the page cache and free it. Caller has to make
* sure the page is locked and that nobody else uses it - or that usage
* is safe.
*/
void __remove_inode_page(struct page *page)
{
        if (PageDirty(page)) BUG();
        remove_page_from_inode_queue(page);
        remove_page_from_hash_queue(page);
        page->mapping = NULL;
}


void __init page_cache_init(unsigned long mempages)
{
        unsigned long htable_size, order;

        htable_size = mempages;
        htable_size *= sizeof(struct page *);
        for(order = 0; (PAGE_SIZE                 ;

        do {
                unsigned long tmp = (PAGE_SIZE
                page_hash_bits = 0;
                while((tmp >>= 1UL) != 0UL)
                        page_hash_bits++;

                page_hash_table = (struct page **)
                        __get_free_pages(GFP_ATOMIC, order);
        } while(page_hash_table == NULL  --order > 0);

        printk("Page-cache hash table entries: %d (order: %ld, %ld bytes)/n",
               (1         if (!page_hash_table)
                panic("Failed to allocate page hash table/n");
        memset((void *)page_hash_table, 0, PAGE_HASH_SIZE * sizeof(struct page *));
}

 

 

[目錄]


塊設備緩衝區結構


塊設備緩衝區用buffer_head結構描述,系統中有NR_SIZES種不一樣尺寸的緩衝區,每種緩衝區的尺寸爲512
grow_buffers(blocksize);
在free_list[]上擴建一頁塊長爲blocksize的備用緩衝區;

bh = create_buffers(page,blocksize,async);
建立塊長爲blocksize的buffer_head結構來描述頁面page.

create_empty_buffers(page,dev,blocksize);
建立塊長爲blocksize,塊設備爲dev的buffer_head結構來描述頁面page

bh = get_unused_buffer_head(async);
取備用的buffer_head結構,async=1容許進程暫時睡眠.

 

struct buffer_head {
        struct buffer_head *b_next; 用於緩衝塊索引的散列鏈
        unsigned long b_blocknr; 該緩衝區在塊設備上的塊號
        unsigned short b_size; 該緩衝區數據塊尺寸
        unsigned short b_list; 在lru_list[]中的序號,表示該緩衝區的使用狀態.
        kdev_t b_dev; 緩衝區所屬的邏輯塊設備

        atomic_t b_count;        引用計數
        kdev_t b_rdev; 所屬的物理塊設備
        unsigned long b_state;
        unsigned long b_flushtime;

        struct buffer_head *b_next_free; 指向下一備用緩衝塊
        struct buffer_head *b_prev_free; 指向前一備用緩衝塊
        struct buffer_head *b_this_page; 指向同一頁面的緩衝塊,造成環形鏈表
        struct buffer_head *b_reqnext; 用於塊設備驅動程序

        struct buffer_head **b_pprev;        用於緩衝塊散列鏈
        char * b_data;        指向緩衝塊的數據區
        struct page *b_page; 緩衝塊的數據區所在的頁面
        void (*b_end_io)(struct buffer_head *bh, int uptodate);
        void *b_private;

        unsigned long b_rsector;
        wait_queue_head_t b_wait;

        struct inode *             b_inode;
        struct list_head     b_inode_buffers;
};

#define NR_SIZES 7

; 這是一張以2爲底的簡易對數表,用於塊長度到free_list[]索引的轉換
static char buffersize_index[65] =
{-1,  0,  1, -1,  2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1,
  4, -1, -1, -1, -1, -1, -1, -1, -1,-1, -1, -1, -1, -1, -1, -1,
  5, -1, -1, -1, -1, -1, -1, -1, -1,-1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1,-1, -1, -1, -1, -1, -1, -1,
  6};

#define BUFSIZE_INDEX(X) ((int) buffersize_index[(X)>>9])
#define MAX_BUF_PER_PAGE (PAGE_CACHE_SIZE / 512) 每頁最多的緩衝塊數(8)
#define NR_RESERVED (2*MAX_BUF_PER_PAGE)
#define MAX_UNUSED_BUFFERS NR_RESERVED+20 /* don't ever have more than this
                                             number of unused buffer heads */

static struct buffer_head *lru_list[NR_LIST];
static int nr_buffers_type[NR_LIST]; 每種尺寸緩衝塊的數量
static unsigned long size_buffers_type[NR_LIST]; 每種尺寸緩衝區的字節總數

static struct buffer_head * unused_list;
static int nr_unused_buffer_heads;

struct bh_free_head {
        struct buffer_head *list;
        spinlock_t lock;
};
static struct bh_free_head free_list[NR_SIZES];

static int grow_buffers(int size)
{
        struct page * page;
        struct buffer_head *bh, *tmp;
        struct buffer_head * insert_point;
        int isize;

        if ((size  511) || (size > PAGE_SIZE)) {
                printk("VFS: grow_buffers: size = %d/n",size);
                return 0;
        }

        page = alloc_page(GFP_BUFFER);
        if (!page)
                goto out;
        LockPage(page);
        bh = create_buffers(page, size, 0);
        if (!bh)
                goto no_buffer_head;

        isize = BUFSIZE_INDEX(size);

        spin_lock(
        insert_point = free_list[isize].list;
        tmp = bh;
        while (1) { 將頁中的每一塊緩衝區插入free_list[isize].list
                if (insert_point) {
                        tmp->b_next_free = insert_point->b_next_free;
                        tmp->b_prev_free = insert_point;
                        insert_point->b_next_free->b_prev_free = tmp;
                        insert_point->b_next_free = tmp;
                } else {
                        tmp->b_prev_free = tmp;
                        tmp->b_next_free = tmp;
                }
                insert_point = tmp;
                if (tmp->b_this_page)
                        tmp = tmp->b_this_page;
                else
                        break;
        }
        tmp->b_this_page = bh; 造成單向環形鏈表
        free_list[isize].list = bh;
        spin_unlock(

        page->buffers = bh; 表示該頁與塊設備緩衝區相關聯
        page->flags  ~(1         lru_cache_add(page); 將該頁加入頁面LRU鏈表
        UnlockPage(page);
        atomic_inc(
        return 1;

no_buffer_head:
        UnlockPage(page);
        page_cache_release(page); 釋放alloc_page()分配的頁面
out:
        return 0;
}
static struct buffer_head * create_buffers(struct page * page, unsigned long size, int async)
{
        struct buffer_head *bh, *head;
        long offset;

try_again:
        head = NULL;
        offset = PAGE_SIZE;
        while ((offset -= size) >= 0) { 從頁面的高端向低端分配地址
                bh = get_unused_buffer_head(async);
                if (!bh)
                        goto no_grow;

                bh->b_dev = B_FREE;  /* Flag as unused */
                bh->b_this_page = head;
                head = bh;

                bh->b_state = 0;
                bh->b_next_free = NULL;
                bh->b_pprev = NULL;
                atomic_set( 0);
                bh->b_size = size;

                set_bh_page(bh, page, offset);

                bh->b_list = BUF_CLEAN;
                bh->b_end_io = NULL;
        }
        return head;
/*
* In case anything failed, we just free everything we got.
*/
no_grow:
        if (head) { 若是在分配中途失敗,則撤消已有分配
                spin_lock(
                do {
                        bh = head;
                        head = head->b_this_page;
                        __put_unused_buffer_head(bh);
                } while (head);
                spin_unlock(

                /* Wake up any waiters ... */
                wake_up( 喚醒下文因wait_event()而睡眠的那些進程
        }

        /*
        * Return failure for non-async IO requests.  Async IO requests
        * are not allowed to fail, so we have to wait until buffer heads
        * become available.  But we don't want tasks sleeping with
        * partially complete buffers, so all were released above.
        */
        if (!async)
                return NULL;

        /* We're _really_ low on memory. Now we just
        * wait for old buffer heads to become free due to
        * finishing IO.  Since this is an async request and
        * the reserve list is empty, we're sure there are
        * async buffer heads in use.
        */
        run_task_queue(

        /*
        * Set our state for sleeping, then check again for buffer heads.
        * This ensures we won't miss a wake_up from an interrupt.
        */
        wait_event(buffer_wait, nr_unused_buffer_heads >= MAX_BUF_PER_PAGE);
        ; 若是nr_unused_buffer_heads >= MAX_BUF_PER_PAGE 則wait_event返回,不然睡眠
        goto try_again;
}
static struct buffer_head * get_unused_buffer_head(int async)
{
        struct buffer_head * bh;

        spin_lock(
        if (nr_unused_buffer_heads > NR_RESERVED) {
                bh = unused_list;
                unused_list = bh->b_next_free;
                nr_unused_buffer_heads--;
                spin_unlock(
                return bh;
        }
        spin_unlock(

        /* This is critical.  We can't swap out pages to get
        * more buffer heads, because the swap-out may need
        * more buffer-heads itself.  Thus SLAB_BUFFER.
        */
        if((bh = kmem_cache_alloc(bh_cachep, SLAB_BUFFER)) != NULL) {
                memset(bh, 0, sizeof(*bh));
                init_waitqueue_head(
                return bh;
        }

        /*
        * If we need an async buffer, use the reserved buffer heads.
        */
        if (async) {
                spin_lock(
                if (unused_list) {
                        bh = unused_list;
                        unused_list = bh->b_next_free;
                        nr_unused_buffer_heads--;
                        spin_unlock(
                        return bh;
                }
                spin_unlock(
        }
#if 0
        /*
        * (Pending further analysis ...)
        * Ordinary (non-async) requests can use a different memory priority
        * to free up pages. Any swapping thus generated will use async
        * buffer heads.
        */
        if(!async
           (bh = kmem_cache_alloc(bh_cachep, SLAB_KERNEL)) != NULL) {
                memset(bh, 0, sizeof(*bh));
                init_waitqueue_head(
                return bh;
        }
#endif

        return NULL;
}
static void create_empty_buffers(struct page *page, kdev_t dev, unsigned long blocksize)
{
        struct buffer_head *bh, *head, *tail;

        head = create_buffers(page, blocksize, 1);
        if (page->buffers)
                BUG();

        bh = head;
        do {
                bh->b_dev = dev;
                bh->b_blocknr = 0;
                bh->b_end_io = NULL;
                tail = bh;
                bh = bh->b_this_page;
        } while (bh);
        tail->b_this_page = head;
        page->buffers = head;
        page_cache_get(page);
}
void set_bh_page (struct buffer_head *bh, struct page *page, unsigned long offset)
{
        bh->b_page = page;
        if (offset >= PAGE_SIZE)
                BUG();
        if (PageHighMem(page))
                /*
                * This catches illegal uses and preserves the offset:
                */
                bh->b_data = (char *)(0 + offset);
        else
                bh->b_data = page_address(page) + offset;
}
static __inline__ void __put_unused_buffer_head(struct buffer_head * bh)
{
        if (bh->b_inode)
                BUG();
        if (nr_unused_buffer_heads >= MAX_UNUSED_BUFFERS) {
                kmem_cache_free(bh_cachep, bh);
        } else {
                bh->b_blocknr = -1;
                init_waitqueue_head(
                nr_unused_buffer_heads++;
                bh->b_next_free = unused_list;
                bh->b_this_page = NULL;
                unused_list = bh;
        }
}

 

 

[目錄]


散列算法


系統在解析路徑時,使用dentry_hashtable來緩衝每一節路徑名對應的dentry目錄項;
dentry_hashtable是由list_head組成的數組,它們與dentry->d_hash相環接,造成短鏈,散列表中的dentry將均分佈於這些短鏈上;
散列表的索引肯定於父目錄項地址和目錄名的hash值;
dentry_hashtable的尺寸由系統內存大小分配,每4M內存分配一個頁面,每一個頁面具備512項索引;
d_hash(dentry,hash)爲散列函數,它將dentry地址和hash值相組合,映射到dentry_hashtable表中,返回相應的散列鏈;
d_rehash(dentry)將dentry加入散列表;
d_drop(dentry)將dentry從散列表中刪除;
d_lookup(dentry,qstr)在散列中找出以dentry做爲父目錄項,名稱爲qstr的目錄項.

系統用下面的方法計算某節路徑名的hash值:

 

        unsigned long hash;

        struct qstr this;

        unsigned int c;

 

        hash = init_name_hash();

        do {

                name++;

                hash = partial_name_hash(c, hash);

                c = *(const unsigned char *)name;

        } while (c  (c != '/'));

 

        this.len = name - (const char *) this.name;

        this.hash = end_name_hash(hash);

        ...

 

有關的代碼以下:

static unsigned int d_hash_mask;

static unsigned int d_hash_shift;

static struct list_head *dentry_hashtable;

 

#define init_name_hash() 0

 

static __inline__ unsigned long partial_name_hash(unsigned long c, unsigned long prevhash)

{

        prevhash = (prevhash > (8*sizeof(unsigned long)-4));

        return prevhash ^ c;

}

 

/* Finally: cut down the number of bits to a int value (and try to avoid losing bits) */

static __inline__ unsigned long end_name_hash(unsigned long hash)

{

        if (sizeof(hash) > sizeof(unsigned int))

                hash += hash >> 4*sizeof(hash);

        return (unsigned int) hash;

}

#define D_HASHBITS     d_hash_shift

#define D_HASHMASK     d_hash_mask

static inline struct list_head * d_hash(struct dentry * parent, unsigned long hash)

{

        hash += (unsigned long) parent / L1_CACHE_BYTES;

        hash = hash ^ (hash >> D_HASHBITS) ^ (hash >> D_HASHBITS*2);

        return dentry_hashtable + (hash  D_HASHMASK);

}

void d_rehash(struct dentry * entry)

{

        struct list_head *list = d_hash(entry->d_parent, entry->d_name.hash);

        spin_lock(

        list_add( list);

        spin_unlock(

}

static __inline__ void d_drop(struct dentry * dentry)

{

        spin_lock(

        list_del(

        INIT_LIST_HEAD(

        spin_unlock(

}

struct dentry * d_lookup(struct dentry * parent, struct qstr * name)

{

        unsigned int len = name->len;

        unsigned int hash = name->hash;

        const unsigned char *str = name->name;

        struct list_head *head = d_hash(parent,hash);

        struct list_head *tmp;

 

        spin_lock(

        tmp = head->next;

        for (;;) {

                struct dentry * dentry = list_entry(tmp, struct dentry, d_hash);

                if (tmp == head)

                        break;

                tmp = tmp->next;

                if (dentry->d_name.hash != hash)

                        continue;

                if (dentry->d_parent != parent)

                        continue;

                if (parent->d_op  parent->d_op->d_compare) {

                        ; 若是文件系統提供了目錄名比較的方法

                        if (parent->d_op->d_compare(parent,  name))

                                continue;

                } else {

                        if (dentry->d_name.len != len)

                                continue;

                        if (memcmp(dentry->d_name.name, str, len))

                                continue;

                }

                __dget_locked(dentry); 增長dentry的引用計數

                dentry->d_flags |= DCACHE_REFERENCED;

                spin_unlock(

                return dentry;

        }

        spin_unlock(

        return NULL;

}

static void __init dcache_init(unsigned long mempages)

{

        struct list_head *d;

        unsigned long order;

        unsigned int nr_hash;

        int i;

 

        /*

        * A constructor could be added for stable state like the lists,

        * but it is probably not worth it because of the cache nature

        * of the dcache.

        * If fragmentation is too bad then the SLAB_HWCACHE_ALIGN

        * flag could be removed here, to hint to the allocator that

        * it should not try to get multiple page regions.

        */

        dentry_cache = kmem_cache_create("dentry_cache",

                                        sizeof(struct dentry),

                                        0,

                                        SLAB_HWCACHE_ALIGN,

                                        NULL, NULL);

        if (!dentry_cache)

                panic("Cannot create dentry cache");

 

#if PAGE_SHIFT         mempages >>= (13 - PAGE_SHIFT);

#endif

        mempages *= sizeof(struct list_head);

        for (order = 0; ((1UL

        do {

                unsigned long tmp;

 

                nr_hash = (1UL                         sizeof(struct list_head);

                d_hash_mask = (nr_hash - 1);

 

                tmp = nr_hash;

                d_hash_shift = 0;

                while ((tmp >>= 1UL) != 0UL)

                        d_hash_shift++;

 

                dentry_hashtable = (struct list_head *)

                        __get_free_pages(GFP_ATOMIC, order);

        } while (dentry_hashtable == NULL  --order >= 0);

        ; 若是order太大,超過了__get_free_pages最大可分配尺寸,則減少order的值重試.

 

        printk("Dentry-cache hash table entries: %d (order: %ld, %ld bytes)/n",

                        nr_hash, order, (PAGE_SIZE

        if (!dentry_hashtable)

                panic("Failed to allocate dcache hash table/n");

 

        d = dentry_hashtable;

        i = nr_hash;

        do {

                INIT_LIST_HEAD(d);

                d++;

                i--;

        } while (i);

}

對opera的註釋加點解釋,讀起來可能會更省力些。

1.爲何要用這個算法

  例如要構造一個文件 /usr/local/cross/my_comp
  這時要沿着上面這個文件名開始依次找直到cross的數據結構,也就是要找到

  /usr/
  /usr/local/
  /usr/local/cross/
  對應的數據結構dentry

  假定咱們已經找到/usr/對應的dentry, 如今必須可以從local找到它對應的dentry,這時就要從名字---->dentry的快速映射,在Linux中通常用哈希映射。

2. 查找方法

  首先,經過d_hash粗分類,找到"local"所在的鏈表,而後順序向下一一匹配。


3.一些操做如opera所述

4.初始化

  首先經過__get_free_pages得到一些頁面,這些頁面構成了全部鏈表頭數組。

[目錄]


permission(inode,mask)


permission(inode,mask)用來測試對文件(inode)是否有(mask)訪問權.

 


; fs/namei.c

int permission(struct inode * inode,int mask)

{

        if (inode->i_op  inode->i_op->permission) {

                ; 若是文件系統定義了自已的受權算法

                int retval;

                lock_kernel();

                retval = inode->i_op->permission(inode, mask);

                unlock_kernel();

                return retval;

        }

        return vfs_permission(inode, mask); 缺省的受權算法

}

int vfs_permission(struct inode * inode,int mask)

{

        int mode = inode->i_mode;

 

        ; 若是對只讀文件系統中的普通文件,目錄文件,符號連接請求寫訪問

        if ((mask  S_IWOTH)  IS_RDONLY(inode)

                (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))

                return -EROFS; /* Nobody gets write access to a read-only fs */

        ; 若是對免疫文件請求寫訪問

        if ((mask  S_IWOTH)  IS_IMMUTABLE(inode))

                return -EACCES; /* Nobody gets write access to an immutable file */

        if (current->fsuid == inode->i_uid)

                mode >>= 6; 若是自已經是文件的擁有者,取文件對擁有者的訪問權

        else if (in_group_p(inode->i_gid))

                mode >>= 3; 若是自已經是文件所在組的成員,取文件對組成員的訪問權

        ; 若是所請求的權限是實際對文件權限的子集或者被賦予了超越特權,則容許訪問

        if (((mode  mask  S_IRWXO) == mask) || capable(CAP_DAC_OVERRIDE))

                return 0;

 

        ; 雖然自已對文件沒有訪問權限,但若是自已被賦予了讀和檢索的特權,

        ; 則容許讀或檢索目錄.

        /* read and search access */

        if ((mask == S_IROTH) ||

            (S_ISDIR(inode->i_mode)   !(mask  ~(S_IROTH | S_IXOTH))))

                if (capable(CAP_DAC_READ_SEARCH))

                        return 0;

 

        return -EACCES;

}

 

; kernel/sys.c

/*

* Check whether we're fsgid/egid or in the supplemental group..

*/

int in_group_p(gid_t grp)

{

        int retval = 1;

        if (grp != current->fsgid)

                retval = supplemental_group_member(grp);

        return retval;

}

static int supplemental_group_member(gid_t grp)

{

        int i = current->ngroups;

 

        if (i) {

                gid_t *groups = current->groups;

                do {

                        if (*groups == grp)

                                return 1;

                        groups++;

                        i--;

                } while (i);

        }

        return 0;

}

 

 

 

[目錄]


IDE硬盤驅動器讀寫


Linux內核在缺省配置下最多支持10個IDE接口,IDE接口用ide_hwif_t結構來描述,每一個IDE接口具備一對主-從驅動器接口,它們用ide_drive_t結構來描述,每一個驅動器接口可接不一樣種類的IDE設備,如IDE硬盤,光驅等,它們用ide_driver_t結構來描述.
每一個驅動器接口包含一個命令請求隊列,用request_queue_t結構來描述,具體的請求用request結構來描述.

多個IDE驅動器能夠共享一箇中斷,共享同一中斷的驅動器造成一個組,用ide_hwgroup_t結構來描述.ide_intr()是全部的ide驅動器所共用的硬件中斷入口,對之對應的ide_hwgroup_t指針將做爲dev_id傳遞給ide_intr.

每次在讀寫某個驅動器以前,須要用ide_set_handler()來設置ide_intr將要調用的中斷函數指針.中斷產生之後,該函數指針被自動清除.

do_rw_disk(drive,rq,block) 從邏輯扇區號block開始向IDE硬盤驅動器drive寫入rq所描述的內容.

如下是硬盤PIO傳輸模式的有關代碼.


; drivers/ide/ide-disk.c
static ide_startstop_t do_rw_disk (ide_drive_t *drive, struct request *rq, unsigned long block)
{
        if (IDE_CONTROL_REG)
                OUT_BYTE(drive->ctl,IDE_CONTROL_REG);
        OUT_BYTE(rq->nr_sectors,IDE_NSECTOR_REG);
        if (drive->select.b.lba) { 若是是邏輯塊尋址模式
                OUT_BYTE(block,IDE_SECTOR_REG);
                OUT_BYTE(block>>=8,IDE_LCYL_REG);
                OUT_BYTE(block>>=8,IDE_HCYL_REG);
                OUT_BYTE(((block>>8)
        } else {
                unsigned int sect,head,cyl,track;
                track = block / drive->sect;
                sect  = block % drive->sect + 1;
                OUT_BYTE(sect,IDE_SECTOR_REG);
                head  = track % drive->head;
                cyl   = track / drive->head;
                OUT_BYTE(cyl,IDE_LCYL_REG);
                OUT_BYTE(cyl>>8,IDE_HCYL_REG);
                OUT_BYTE(head|drive->select.all,IDE_SELECT_REG);
        }
        if (rq->cmd == READ) {{
                ide_set_handler(drive,  WAIT_CMD, NULL); WAIT_CMD爲10秒超時
                OUT_BYTE(drive->mult_count ? WIN_MULTREAD : WIN_READ, IDE_COMMAND_REG);
                return ide_started;
        }
        if (rq->cmd == WRITE) {
                ide_startstop_t startstop;
                OUT_BYTE(drive->mult_count ? WIN_MULTWRITE : WIN_WRITE, IDE_COMMAND_REG);
                if (ide_wait_stat( drive, DATA_READY, drive->bad_wstat, WAIT_DRQ)) {
                        printk(KERN_ERR "%s: no DRQ after issuing %s/n", drive->name,
                                drive->mult_count ? "MULTWRITE" : "WRITE");
                        return startstop;
                }
                if (!drive->unmask)
                        __cli();        /* local CPU only */
                if (drive->mult_count) { 若是容許多扇區傳送
                        ide_hwgroup_t *hwgroup = HWGROUP(drive);
                        /*
                        * Ugh.. this part looks ugly because we MUST set up
                        * the interrupt handler before outputting the first block
                        * of data to be written.  If we hit an error (corrupted buffer list)
                        * in ide_multwrite(), then we need to remove the handler/timer
                        * before returning.  Fortunately, this NEVER happens (right?).
                        *
                        * Except when you get an error it seems...
                        */
                        hwgroup->wrq = *rq; /* scratchpad */
                        ide_set_handler (drive,  WAIT_CMD, NULL);
                        if (ide_multwrite(drive, drive->mult_count)) {
                                unsigned long flags;
                                spin_lock_irqsave( flags);
                                hwgroup->handler = NULL;
                                del_timer(
                                spin_unlock_irqrestore( flags);
                                return ide_stopped;
                        }
                } else {
                        ide_set_handler (drive,  WAIT_CMD, NULL);
                        idedisk_output_data(drive, rq->buffer, SECTOR_WORDS); 寫入一扇區SECTOR_WORDS=512/4
                }
                return ide_started;
        }
        printk(KERN_ERR "%s: bad command: %d/n", drive->name, rq->cmd);
        ide_end_request(0, HWGROUP(drive));
        return ide_stopped;
}
void ide_set_handler (ide_drive_t *drive, ide_handler_t *handler,
                      unsigned int timeout, ide_expiry_t *expiry)
{
        unsigned long flags;
        ide_hwgroup_t *hwgroup = HWGROUP(drive);

        spin_lock_irqsave( flags);
        if (hwgroup->handler != NULL) {
                printk("%s: ide_set_handler: handler not null; old=%p, new=%p/n",
                        drive->name, hwgroup->handler, handler);
        }
        hwgroup->handler        = handler;
        hwgroup->expiry                = expiry;
        hwgroup->timer.expires        = jiffies + timeout;
        add_timer(
        spin_unlock_irqrestore( flags);
}
static inline void idedisk_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
{
        if (drive->bswap) {
                idedisk_bswap_data(buffer, wcount);
                ide_output_data(drive, buffer, wcount);
                idedisk_bswap_data(buffer, wcount);
        } else
                ide_output_data(drive, buffer, wcount);
}
void ide_output_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
{
        byte io_32bit = drive->io_32bit;

        if (io_32bit) {
#if SUPPORT_VLB_SYNC
                if (io_32bit  2) {
                        unsigned long flags;
                        __save_flags(flags);        /* local CPU only */
                        __cli();                /* local CPU only */
                        do_vlb_sync(IDE_NSECTOR_REG);
                        outsl(IDE_DATA_REG, buffer, wcount);
                        __restore_flags(flags);        /* local CPU only */
                } else
#endif /* SUPPORT_VLB_SYNC */
                        outsl(IDE_DATA_REG, buffer, wcount);
        } else {
#if SUPPORT_SLOW_DATA_PORTS
                if (drive->slow) {
                        unsigned short *ptr = (unsigned short *) buffer;
                        while (wcount--) {
                                outw_p(*ptr++, IDE_DATA_REG);
                                outw_p(*ptr++, IDE_DATA_REG);
                        }
                } else
#endif /* SUPPORT_SLOW_DATA_PORTS */
                        outsw(IDE_DATA_REG, buffer, wcount        }
}
int ide_multwrite (ide_drive_t *drive, unsigned int mcount)
{
        ide_hwgroup_t        *hwgroup= HWGROUP(drive);

        /*
        *        This may look a bit odd, but remember wrq is a copy of the
        *        request not the original. The pointers are real however so the
        *        bh's are not copies. Remember that or bad stuff will happen
        *
        *        At the point we are called the drive has asked us for the
        *        data, and its our job to feed it, walking across bh boundaries
        *        if need be.
        */

        struct request        *rq =

          do {
                unsigned long flags;
                  unsigned int nsect = rq->current_nr_sectors;
                if (nsect > mcount)
                        nsect = mcount;
                mcount -= nsect;
                ; 這時mcount爲剩餘的必需傳送的扇區數
                idedisk_output_data(drive, rq->buffer, nsect                spin_lock_irqsave( flags);        /* Is this really necessary? */
#ifdef CONFIG_BLK_DEV_PDC4030
                rq->sector += nsect;
#endif
                if (((long)(rq->nr_sectors -= nsect))                         spin_unlock_irqrestore( flags);
                        break;
                }
                if ((rq->current_nr_sectors -= nsect) == 0) {
                        if ((rq->bh = rq->bh->b_reqnext) != NULL) {{
                                rq->current_nr_sectors = rq->bh->b_size>>9;
                                rq->buffer             = rq->bh->b_data;
                        } else {
                                spin_unlock_irqrestore( flags);
                                printk("%s: buffer list corrupted (%ld, %ld, %d)/n",
                                        drive->name, rq->current_nr_sectors,
                                        rq->nr_sectors, nsect);
                                ide_end_request(0, hwgroup);
                                return 1;
                        }
                } else {
                        /* Fix the pointer.. we ate data */
                        rq->buffer += nsect                 }
                spin_unlock_irqrestore( flags);
        } while (mcount);
        return 0;
}

; IDE接口共用中斷入口
void ide_intr (int irq, void *dev_id, struct pt_regs *regs)
{
        unsigned long flags;
        ide_hwgroup_t *hwgroup = (ide_hwgroup_t *)dev_id;
        ide_hwif_t *hwif;
        ide_drive_t *drive;
        ide_handler_t *handler;
        ide_startstop_t startstop;

        spin_lock_irqsave( flags);
        hwif = hwgroup->hwif;

        if (!ide_ack_intr(hwif)) {
                spin_unlock_irqrestore( flags);
                return;
        }

        if ((handler = hwgroup->handler) == NULL || hwgroup->poll_timeout != 0) {
                /*
                * Not expecting an interrupt from this drive.
                * That means this could be:
                *        (1) an interrupt from another PCI device
                *        sharing the same PCI INT# as us.
                * or        (2) a drive just entered sleep or standby mode,
                *        and is interrupting to let us know.
                * or        (3) a spurious interrupt of unknown origin.
                *
                * For PCI, we cannot tell the difference,
                * so in that case we just ignore it and hope it goes away.
                */
#ifdef CONFIG_BLK_DEV_IDEPCI
                if (IDE_PCI_DEVID_EQ(hwif->pci_devid, IDE_PCI_DEVID_NULL))
#endif        /* CONFIG_BLK_DEV_IDEPCI */
                {
                        /*
                        * Probably not a shared PCI interrupt,
                        * so we can safely try to do something about it:
                        */
                        unexpected_intr(irq, hwgroup);
#ifdef CONFIG_BLK_DEV_IDEPCI
                } else {
                        /*
                        * Whack the status register, just in case we have a leftover pending IRQ.
                        */
                        (void) IN_BYTE(hwif->io_ports[IDE_STATUS_OFFSET]);
#endif /* CONFIG_BLK_DEV_IDEPCI */
                }
                spin_unlock_irqrestore( flags);
                return;
        }
        drive = hwgroup->drive;
        if (!drive) {
                /*
                * This should NEVER happen, and there isn't much we could do about it here.
                */
                spin_unlock_irqrestore( flags);
                return;
        }
        if (!drive_is_ready(drive)) {
                /*
                * This happens regularly when we share a PCI IRQ with another device.
                * Unfortunately, it can also happen with some buggy drives that trigger
                * the IRQ before their status register is up to date.  Hopefully we have
                * enough advance overhead that the latter isn't a problem.
                */
                spin_unlock_irqrestore( flags);
                return;
        }
        if (!hwgroup->busy) {
                hwgroup->busy = 1;        /* paranoia */
                printk("%s: ide_intr: hwgroup->busy was 0 ??/n", drive->name);
        }
        hwgroup->handler = NULL;
        del_timer(
        spin_unlock(

        if (drive->unmask)
                ide__sti();        /* local CPU only */
        startstop = handler(drive);                /* service this interrupt, may set handler for next interrupt */
        spin_lock_irq(

        /*
        * Note that handler() may have set things up for another
        * interrupt to occur soon, but it cannot happen until
        * we exit from this routine, because it will be the
        * same irq as is currently being serviced here, and Linux
        * won't allow another of the same (on any CPU) until we return.
        */
        set_recovery_timer(HWIF(drive));
        drive->service_time = jiffies - drive->service_start;
        if (startstop == ide_stopped) {
                if (hwgroup->handler == NULL) {        /* paranoia */
                        hwgroup->busy = 0;
                        ide_do_request(hwgroup, hwif->irq);
                } else {
                        printk("%s: ide_intr: huh? expected NULL handler on exit/n", drive->name);
                }
        }
        spin_unlock_irqrestore( flags);
}
; 單個扇區寫入以後的中斷處理
static ide_startstop_t write_intr (ide_drive_t *drive)
{
        byte stat;
        int i;
        ide_hwgroup_t *hwgroup = HWGROUP(drive);
        struct request *rq = hwgroup->rq;

        if (!OK_STAT(stat=GET_STAT(),DRIVE_READY,drive->bad_wstat)) {
                printk("%s: write_intr error1: nr_sectors=%ld, stat=0x%02x/n", drive->name, rq->nr_sectors, stat);
        } else {
                if ((rq->nr_sectors == 1) ^ ((stat  DRQ_STAT) != 0)) {
                        rq->sector++;
                        rq->buffer += 512;
                        rq->errors = 0;
                        i = --rq->nr_sectors;
                        --rq->current_nr_sectors;
                        if (((long)rq->current_nr_sectors)                                 ide_end_request(1, hwgroup);
                        if (i > 0) {
                                idedisk_output_data (drive, rq->buffer, SECTOR_WORDS);
                                ide_set_handler (drive,  WAIT_CMD, NULL);
                                return ide_started;
                        }
                        return ide_stopped;
                }
                return ide_stopped;        /* the original code did this here (?) */
        }
        return ide_error(drive, "write_intr", stat);
}
; 多重扇區寫入後的中斷處理
static ide_startstop_t multwrite_intr (ide_drive_t *drive)
{
        byte stat;
        int i;
        ide_hwgroup_t *hwgroup = HWGROUP(drive);
        struct request *rq =

        if (OK_STAT(stat=GET_STAT(),DRIVE_READY,drive->bad_wstat)) {
                if (stat  DRQ_STAT) {
                        /*
                        *        The drive wants data. Remember rq is the copy
                        *        of the request
                        */
                        if (rq->nr_sectors) {
                                if (ide_multwrite(drive, drive->mult_count))
                                        return ide_stopped;
                                ide_set_handler (drive,  WAIT_CMD, NULL);
                                return ide_started;
                        }
                } else {
                        /*
                        *        If the copy has all the blocks completed then
                        *        we can end the original request.
                        */
                        if (!rq->nr_sectors) {        /* all done? */
                                rq = hwgroup->rq;
                                for (i = rq->nr_sectors; i > 0;){
                                        i -= rq->current_nr_sectors;
                                        ide_end_request(1, hwgroup);
                                }
                                return ide_stopped;
                        }
                }
                return ide_stopped;        /* the original code did this here (?) */
        }
        return ide_error(drive, "multwrite_intr", stat);
}
; 讀扇區的中斷處理
static ide_startstop_t read_intr (ide_drive_t *drive)
{
        byte stat;
        int i;
        unsigned int msect, nsect;
        struct request *rq;

        /* new way for dealing with premature shared PCI interrupts */
        if (!OK_STAT(stat=GET_STAT(),DATA_READY,BAD_R_STAT)) {
                if (stat  (ERR_STAT|DRQ_STAT)) {
                        return ide_error(drive, "read_intr", stat);
                }
                /* no data yet, so wait for another interrupt */
                ide_set_handler(drive,  WAIT_CMD, NULL);
                return ide_started;
        }
        msect = drive->mult_count;

read_next:
        rq = HWGROUP(drive)->rq;
        if (msect) {
                if ((nsect = rq->current_nr_sectors) > msect)
                        nsect = msect;
                msect -= nsect;
        } else
                nsect = 1;
        idedisk_input_data(drive, rq->buffer, nsect * SECTOR_WORDS);
        rq->sector += nsect;
        rq->buffer += nsect        rq->errors = 0;
        i = (rq->nr_sectors -= nsect);
        if (((long)(rq->current_nr_sectors -= nsect))                 ide_end_request(1, HWGROUP(drive));
        if (i > 0) {
                if (msect)
                        goto read_next;
                ide_set_handler (drive,  WAIT_CMD, NULL);
                return ide_started;
        }
        return ide_stopped;
}
static inline void idedisk_input_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
{
        ide_input_data(drive, buffer, wcount);
        if (drive->bswap)
                idedisk_bswap_data(buffer, wcount);
}
void ide_input_data (ide_drive_t *drive, void *buffer, unsigned int wcount)
{
        byte io_32bit = drive->io_32bit;

        if (io_32bit) {
#if SUPPORT_VLB_SYNC
                if (io_32bit  2) {
                        unsigned long flags;
                        __save_flags(flags);        /* local CPU only */
                        __cli();                /* local CPU only */
                        do_vlb_sync(IDE_NSECTOR_REG);
                        insl(IDE_DATA_REG, buffer, wcount);
                        __restore_flags(flags);        /* local CPU only */
                } else
#endif /* SUPPORT_VLB_SYNC */
                        insl(IDE_DATA_REG, buffer, wcount);
        } else {
#if SUPPORT_SLOW_DATA_PORTS
                if (drive->slow) {
                        unsigned short *ptr = (unsigned short *) buffer;
                        while (wcount--) {
                                *ptr++ = inw_p(IDE_DATA_REG);
                                *ptr++ = inw_p(IDE_DATA_REG);
                        }
                } else
#endif /* SUPPORT_SLOW_DATA_PORTS */
                        insw(IDE_DATA_REG, buffer, wcount        }
}


atomic_t queued_sectors;

#define blk_finished_io(nsects)                                /
        atomic_sub(nsects,                 /
        if (atomic_read(                 printk("block: queued_sectors                 atomic_set( 0);                /
        }

static inline void blkdev_dequeue_request(struct request * req)
{
        list_del(
}
void ide_end_request (byte uptodate, ide_hwgroup_t *hwgroup)
{
        struct request *rq;
        unsigned long flags;

        spin_lock_irqsave( flags);
        rq = hwgroup->rq;

        if (!end_that_request_first(rq, uptodate, hwgroup->drive->name)) {
                add_blkdev_randomness(MAJOR(rq->rq_dev));
                blkdev_dequeue_request(rq);
                hwgroup->rq = NULL;
                end_that_request_last(rq);
        }
        spin_unlock_irqrestore( flags);
}
int end_that_request_first (struct request *req, int uptodate, char *name)
{
        struct buffer_head * bh;
        int nsect;

        req->errors = 0;
        if (!uptodate)
                printk("end_request: I/O error, dev %s (%s), sector %lu/n",
                        kdevname(req->rq_dev), name, req->sector);

        if ((bh = req->bh) != NULL) {
                nsect = bh->b_size >> 9;
                blk_finished_io(nsect);
                req->bh = bh->b_reqnext;
                bh->b_reqnext = NULL;
                bh->b_end_io(bh, uptodate);
                if ((bh = req->bh) != NULL) {
                        req->hard_sector += nsect;
                        req->hard_nr_sectors -= nsect;
                        req->sector = req->hard_sector;
                        req->nr_sectors = req->hard_nr_sectors;

                        req->current_nr_sectors = bh->b_size >> 9;
                        if (req->nr_sectors current_nr_sectors) {
                                req->nr_sectors = req->current_nr_sectors;
                                printk("end_request: buffer-list destroyed/n");
                        }
                        req->buffer = bh->b_data;
                        return 1;
                }
        }
        return 0;
}
void end_that_request_last(struct request *req)
{
        if (req->sem != NULL)
                up(req->sem);

        blkdev_release_request(req);
}
void inline blkdev_release_request(struct request *req)
{
        request_queue_t *q = req->q;
        int rw = req->cmd;

        req->rq_status = RQ_INACTIVE;
        req->q = NULL;

        /*
        * Request may not have originated from ll_rw_blk. if not,
        * asumme it has free buffers and check waiters
        */
        if (q) {
                /*
                * we've released enough buffers to start I/O again
                */
                if (waitqueue_active(
                     atomic_read(                         wake_up(

                /*
                * Add to pending free list and batch wakeups
                */
                list_add(

                if (++q->pending_free[rw] >= batch_requests) {
                        int wake_up = q->pending_free[rw];
                        blk_refill_freelist(q, rw);
                        wake_up_nr( wake_up);
                }
        }
}
void inline blk_refill_freelist(request_queue_t *q, int rw)
{
        if (q->pending_free[rw]) {
                list_splice(
                INIT_LIST_HEAD(
                q->pending_free[rw] = 0;
        }
}

 

 

[目錄]


驅動

 

[目錄]


PCI


PCI是一種普遍採用的總線標準,它提供了優於其餘總線標準(好比EISA)的特性
。在大多數奔騰主板上,PCI是高速、高帶寬(32-bit和64-bit)、處理器無關的
總線。對PCI的支持第一次加入Linux中時,其內核接口是PCI BIOS32函數的堆砌
。這樣作有幾個問題:

* PCI BIOS僅存在於PC上;
* PCI BIOS只表明特定的結構,非PC類機器的某些PCI設置不能用PCI BIOS來描述

* 個別機子的PCI BIOS函數不象預期的那樣工做。

Linux 2.2提供了一個通用的PCI接口。Linux x86內核實際上努力直接驅動硬件,
只有當它發現某些東西不能理解時,它纔會調用PCI BIOS32函數。
驅動程序能夠繼續使用老的PCI接口,可是爲了兼容未來的內核,可能須要更新。
若是驅動程序將要跨平臺工做,那就更加須要更新了。
多數新、老函數有簡單的對應關係。PCI BIOS基於總線號/設備號/功能號的思想
,而新的代碼使用pci_bus和pci_dev結構。第一個新PCI函數是:

pci_present()

這個函數檢查機器是否存在一條或更多的PCI總線。老內核有一個
pcibios_present()函數,它們的用法徹底相同。

確認PCI存在以後,你能夠掃描PCI總線來查找設備。PCI設備經過幾個配置寄存器
來標識,主要是供應商ID和設備ID。
每一個供應商被分配了一個惟一的標識(ID),而且假設供應商給他們的設備(板
子、芯片等)分配惟一的設備ID。PCI的一個好處是它提供了版本和編程接口信息
,所以能夠發現板子的變化。

在Linux 2.2中,掃描PCI總線通常用pci_find_device()函數。範例以下:

struct pci_dev *pdev = NULL;
while ((pdev = pci_find_device(PCI_MY_VENDOR,
PCI_MY_DEVICE, pdev)) != NULL)
{
/* Found a device */
setup_device(pdev);
}

pci_find_device()有3個參數:第一個是供應商ID,第二個是設備ID,第三個是
函數的返回值,NULL表示你想從頭開始查找。在這個例子中,對找到的設備調用
setup_device()來進行設置。
另外一個值得高興的事情,是PCI爲你處理了全部資源配置工做。通常來講PCI BIOS
具體作這些工做,可是在其餘平臺上,這項工做由固件或者體系結構相關的Linux
代碼來作。到你的驅動程序查找PCI卡的時候,它已經被分配了系統資源。
Linux在pci_dev結構中提供了PCI相關的核心信息。同時還容許讀寫每一個卡的PCI
配置空間。當你能夠直接查找資源數據時應該當心,對許多系統來講,卡上配置
的數據與內核提供的數據並不相符。由於許多非PC機器有多條PCI總線,PCI總線
以設備卡不知道的方式映射到系統中。

Linux直接提供了IRQ和PCI BARs(基址寄存器)。爲了不代碼在非PC平臺上出
現意外,你應該老是使用內核提供的數據。下面代碼列出了setup_device()例程

Listing One: The setup_device () Function
void setup_device(struct pci_dev *dev)
{
int io_addr = dev->base_address[0] & PCI_BASE_ADDRESS_IO_MASK;
int irq = dev->irq;
u8 rev;

pci_read_config_byte(dev, PCI_REVISION_ID, &rev);

if (rev<64)
printk("Found a WonderWidget 500 at I/O 0x%04X, IRQ %d./n",
io_addr, irq);
else
printk("Found a WonderWidget 600 at I/O 0x%04X, IRQ %d./n",
io_addr, irq);

/* Check for a common BIOS problem - if you
* expect an IRQ you might not get it */
if (irq==0)
{
printk(KERN_ERR "BIOS has not assigned the WonderWidget"
" an interrupt./n");
return;
}

/* Now do the board initialization knowing the resources */
init_device(io_addr, irq, rev<64 ? 0 : 1);

pci_set_master(dev);
}

當你的卡被BIOS配置後,某些特性可能會被屏蔽掉。好比,多數BIOS都會清掉「
master」位,這致使板卡不能隨意向主存中拷貝數據。Linux 2.2提供了一個輔助
函數:

pci_set_master(struct pci_dev *)

這個函數會檢查是否須要設置標誌位,若是須要,則會將「master」位置位。
例子函數setup_device還使用了pci_read_config_byte來讀取配置空間數據。內
核提供了一整套與配置空間相關的函數:

pci_read_config_byte,
pci_read_config_word,
和pci_read_config_dword

分別從配置空間獲取8,16和32位數據;

pci_write_config_byte,
pci_write_config_word,
和pci_write_config_dword

分別向配置空間寫入8,16和32位數據。PCI配置空間獨立於I/O和內存空間,只能
經過這些函數訪問。

最後一組有用的PCI函數以不一樣的方式掃描PCI總線。pci_find_class查找符合給
定類別(class)的設備。PCI規範把設備分爲不一樣的類別,你能夠根據類別查找
設備。例如,爲了查找一個USB控制器,能夠用

struct pci_dev *pdev = NULL;
while((pdev=pci_find_class
(PCI_CLASS_SERIAL_USB <<8, pdev))!=NULL)
{
u8 type;
pci_read_config_byte(dev,
PCI_CLASS_PROG, &type);
if(type!=0)
continue;
/* FOUND IT */
}

另外一個例子是I2O。這時,供應商ID只用來肯定板卡的實際類型(type),偶爾用
來對付特定板卡的bug。
掃描PCI設備的最後一種途徑是pci_find_slot,使你按照特定的順序掃描PCI插槽
和功能。它不多使用,可是,若是你要控制查找某一類型設備時掃描PCI總線的順
序,你能夠用它。這種狀況一般出如今你須要遵守主板BIOS報告設備的順序時,
或者你想使Linux和非Linux驅動程序以相同的順序報告設備時。傳遞給
pci_find_slot()的是總線號slot和設備-功能號function(slot<<3 | function
)。

PCI中斷和其餘注意事項

PCI總線一個重要的概念是共享中斷處理,這在ISA總線設備中通常是看不到的。
PCI總線中斷也是電平觸發的(level-triggered),也就是說,中斷一直在那裏
,直到設備去清除它。這些特性給驅動程序處理中斷加上了一些重要的限制。
驅動程序註冊PCI中斷時,老是應該帶上SA_SHIRQ標誌,用來指明中斷線是能夠共
享的。若是不這樣作,那麼系統中的其餘設備有可能不能正常工做,用戶也可能
遇到麻煩。

因爲中斷是共享的,PCI設備驅動程序和內核都須要與每一箇中斷處理例程進行溝通
的方法。你必須用一個非空(non-NULL)的dev_id來註冊共享中斷,不然,當你
須要用free_irq來釋放一箇中斷時,內核不能區分不一樣的中斷處理例程。dev_id
被送到中斷處理例程,所以它很是重要。例如,你能夠這樣:

if (request_irq(dev->irq, dev_interrupt,
SA_SHIRQ, "wonderwidget",
dev))
return -EAGAIN;

結束時,用下面的語句來正確釋放中斷:

free_irq(dev->irq, dev)

中斷處理例程被調用時收到dev參數,這使事情很簡單了。你沒必要搜尋使用該中斷
的設備,一般能夠這樣作:

Listing Two: Using the dev_id
static void dev_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct wonderwidget *dev = dev_id;
u32 status;

/* It is important to exit interrupt handlers
* that are not for us as fast as possible */

if((status=inl(dev->port))==0) /* Not our interrupt */
return;

if(status&1)
handle_rx_intr(dev);
....
}

你必須老是當心處理中斷。永遠不要在安裝中斷處理例程以前產生中斷。由於PCI
中斷是電平觸發的,若是你產生了中斷而又不能處理它,可能會致使死機。這意
味着寫初始化代碼時必須特別當心,你必須在打開設備的中斷以前註冊中斷處理
例程。一樣,關閉時必須在註銷中斷處理例程以前屏蔽設備的中斷。
與ISA總線相比,Linux對PCI總線的支持用到較多的函數,而且要當心處理中斷。
做爲回報,不須要你的介入,系統把一切都配置好了。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[目錄]


loopback.c


各位大俠,最近我看Linux源碼中的網絡驅動部分。
先從loopback.c入手的。
loopback.c中的loopback_xmit函數中有這麼一段:
static int loopback_xmit(struct sk_buff * skb,struct net_device * dev)
{
        struct net_device_stats * stats = (struct net_device_stats *)dev_priv;

        if (atomic_read(&skb->users)!=1){

/*判斷有幾我的用skb. 是會有多出用skb,例如一邊運行一邊sniff.有些時候會修改skb, 這就要clone,若是這/個skb也被其餘人用了.. */

                struct sk_buff * skb2 = skb;
                skb=skb_clone(skb,GFP_ATOMIC);
                if(skb==NULL){
                        kfree_skb(skb2);
                        return 0;/*這裏系統內存不足,爲何不報錯?由於對kernel來講,mem 不夠不是錯,是會出現的實際狀況,. 在這裏的處理方式就是把這個包drop調.不loopback了. */
                }
                kfree_skb(skb2);
        }
        else
                skb_orphan(skb);/*查<linux/skbuff.h>中定義:
                                skb_orphan ---- orphan a buffer
                                @skb: buffer to orphan
                                If a buffer currently has an owner then we
                                call the owner's destructor function and
                                make the @skb unowned.The buffer continues
                                to exist but is no longer charged to its
                                former owner
                                那麼skb_orphan之後,原來skb所指向的sk_buff
                                結構如何使用呢?skb是否成了一個空指針?
                                skb_orphan和kfree_skb有什麼本質的區別?
                                其實這裏應該不是free調的.仍是能夠用的.可是取消
                                原來的owner的引用而已. */
        .
        .
        .
}

[目錄]


網卡驅動編寫


    工做須要寫了咱們公司一塊網卡的Linux驅動程序。經歷一個從無到有的過程,
深感技術交流的重要。Linux做爲挑戰微軟壟斷的強有力武器,日益受到你們的喜
愛。真但願她能在中國迅速成長。把程序文檔貼出來,但願和你們探討Linux技術
和應用,促進Linux在中國的普及。
    本文可隨意轉載,但請不要在盈利性出版物上刊登。

------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------

Linux操做系統網絡驅動程序編寫

一.Linux系統設備驅動程序概述
  1.1 Linux設備驅動程序分類
  1.2 編寫驅動程序的一些基本概念
二.Linux系統網絡設備驅動程序
  2.1 網絡驅動程序的結構
  2.2 網絡驅動程序的基本方法
  2.3 網絡驅動程序中用到的數據結構
  2.4 經常使用的系統支持
三.編寫Linux網絡驅動程序中可能遇到的問題
  3.1 中斷共享
  3.2 硬件發送忙時的處理
  3.3 流量控制(flow control)
  3.4 調試
四.進一步的閱讀
五.雜項

 


一.Linux系統設備驅動程序概述
1.1 Linux設備驅動程序分類
    Linux設備驅動程序在Linux的內核源代碼中佔有很大的比例,源代碼的長度日
益增長,主要是驅動程序的增長。在Linux內核的不斷升級過程當中,驅動程序的結構
仍是相對穩定。在2.0.xx到2.2.xx的變更裏,驅動程序的編寫作了一些改變,可是
從2.0.xx的驅動到2.2.xx的移植只需作少許的工做。

    Linux系統的設備分爲字符設備(char device),塊設備(block device)和網絡
設備(network device)三種。字符設備是指存取時沒有緩存的設備。塊設備的讀寫
都有緩存來支持,而且塊設備必須可以隨機存取(random access),字符設備則沒有
這個要求。典型的字符設備包括鼠標,鍵盤,串行口等。塊設備主要包括硬盤軟盤
設備,CD-ROM等。一個文件系統要安裝進入操做系統必須在塊設備上。

    網絡設備在Linux裏作專門的處理。Linux的網絡系統主要是基於BSD unix的socket
機制。在系統和驅動程序之間定義有專門的數據結構(sk_buff)進行數據的傳遞。系
統裏支持對發送數據和接收數據的緩存,提供流量控制機制,提供對多協議的支持。


1.2 編寫驅動程序的一些基本概念
    不管是什麼操做系統的驅動程序,都有一些通用的概念。操做系統提供給驅動
程序的支持也大體相同。下面簡單介紹一下網絡設備驅動程序的一些基本要求。

1.2.1 發送和接收
    這是一個網絡設備最基本的功能。一塊網卡所作的無非就是收發工做。因此驅
動程序裏要告訴系統你的發送函數在哪裏,系統在有數據要發送時就會調用你的發
送程序。還有驅動程序因爲是直接操縱硬件的,因此網絡硬件有數據收到最早能得
到這個數據的也就是驅動程序,它負責把這些原始數據進行必要的處理而後送給系
統。這裏,操做系統必需要提供兩個機制,一個是找到驅動程序的發送函數,一個
是驅動程序把收到的數據送給系統。

1.2.2 中斷
    中斷在現代計算機結構中有重要的地位。操做系統必須提供驅動程序響應中斷
的能力。通常是把一箇中斷處理程序註冊到系統中去。操做系統在硬件中斷髮生後
調用驅動程序的處理程序。Linux支持中斷的共享,即多個設備共享一箇中斷。

1.2.3 時鐘
    在實現驅動程序時,不少地方會用到時鐘。如某些協議裏的超時處理,沒有中
斷機制的硬件的輪詢等。操做系統應爲驅動程序提供定時機制。通常是在預約的時
間過了之後回調註冊的時鐘函數。在網絡驅動程序中,若是硬件沒有中斷功能,定
時器能夠提供輪詢(poll)方式對硬件進行存取。或者是實現某些協議時須要的超時
重傳等。

二.Linux系統網絡設備驅動程序

2.1 網絡驅動程序的結構
    全部的Linux網絡驅動程序遵循通用的接口。設計時採用的是面向對象的方法。
一個設備就是一個對象(device 結構),它內部有本身的數據和方法。每個設備的
方法被調用時的第一個參數都是這個設備對象自己。這樣這個方法就能夠存取自身
的數據(相似面向對象程序設計時的this引用)。
    一個網絡設備最基本的方法有初始化、發送和接收。

     -------------------            ---------------------
    |deliver packets    |          |receive packets queue|
    |(dev_queue_xmit()) |          |them(netif_rx())     |
     -------------------            ---------------------
        |         |                      /          /
        /         /                      |          |
    -------------------------------------------------------
   | methods and variables(initialize,open,close,hard_xmit,|
   | interrupt handler,config,resources,status...)         |
    -------------------------------------------------------
         |        |                      /          /
         /        /                      |          |
      -----------------              ----------------------
     |send to hardware |            |receivce from hardware|
      -----------------              ----------------------
         |        |                      /          /
         /        /                      |          |
     -----------------------------------------------------
    |                  hardware media                     |
     -----------------------------------------------------

    初始化程序完成硬件的初始化、device中變量的初始化和系統資源的申請。發送
程序是在驅動程序的上層協議層有數據要發送時自動調用的。通常驅動程序中不對發
送數據進行緩存,而是直接使用硬件的發送功能把數據發送出去。接收數據通常是通
過硬件中斷來通知的。在中斷處理程序裏,把硬件幀信息填入一個skbuff結構中,然

------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------

後調用netif_rx()傳遞給上層處理。


2.2 網絡驅動程序的基本方法
    網絡設備作爲一個對象,提供一些方法供系統訪問。正是這些有統一接口的方法,
掩蔽了硬件的具體細節,讓系統對各類網絡設備的訪問都採用統一的形式,作到硬件
無關性。
    下面解釋最基本的方法。
2.2.1 初始化(initialize)
    驅動程序必須有一個初始化方法。在把驅動程序載入系統的時候會調用這個初
始化程序。它作如下幾方面的工做。檢測設備。在初始化程序裏你能夠根據硬件的
特徵檢查硬件是否存在,而後決定是否啓動這個驅動程序。配置和初始化硬件。在
初始化程序裏你能夠完成對硬件資源的配置,好比即插即用的硬件就能夠在這個時
候進行配置(Linux內核對PnP功能沒有很好的支持,能夠在驅動程序裏完成這個功
能)。配置或協商好硬件佔用的資源之後,就能夠向系統申請這些資源。有些資源是
能夠和別的設備共享的,如中斷。有些是不能共享的,如IO、DMA。接下來你要初始
化device結構中的變量。最後,你能夠讓硬件正式開始工做。

2.2.2 打開(open)
    open這個方法在網絡設備驅動程序裏是網絡設備被激活的時候被調用(即設備狀
態由down-->up)。因此實際上不少在initialize中的工做能夠放到這裏來作。好比資
源的申請,硬件的激活。若是dev->open返回非0(error),則硬件的狀態仍是down。
    open方法另外一個做用是若是驅動程序作爲一個模塊被裝入,則要防止模塊卸載時
設備處於打開狀態。在open方法裏要調用MOD_INC_USE_COUNT宏。

2.2.3 關閉(stop)
    close方法作和open相反的工做。能夠釋放某些資源以減小系統負擔。close是在
設備狀態由up轉爲down時被調用的。另外若是是作爲模塊裝入的驅動程序,close裏
應該調用MOD_DEC_USE_COUNT,減小設備被引用的次數,以使驅動程序能夠被卸載。
另外close方法必須返回成功(0==success)。

2.2.4 發送(hard_start_xmit)
    全部的網絡設備驅動程序都必須有這個發送方法。在系統調用驅動程序的xmit
時,發送的數據放在一個sk_buff結構中。通常的驅動程序把數據傳給硬件發出去。
也有一些特殊的設備好比loopback把數據組成一個接收數據再回送給系統,或者
dummy設備直接丟棄數據。
    若是發送成功,hard_start_xmit方法裏釋放sk_buff,返回0(發送成功)。若是
設備暫時沒法處理,好比硬件忙,則返回1。這時若是dev->tbusy置爲非0,則系統
認爲硬件忙,要等到dev->tbusy置0之後纔會再次發送。tbusy的置0任務通常由中斷
完成。硬件在發送結束後產生中斷,這時能夠把tbusy置0,而後用mark_bh()調用通
知系統能夠再次發送。在發送不成功的狀況下,也能夠不置dev->tbusy爲非0,這樣
系統會不斷嘗試重發。若是hard_start_xmit發送不成功,則不要釋放sk_buff。
    傳送下來的sk_buff中的數據已經包含硬件須要的幀頭。因此在發送方法裏不需
要再填充硬件幀頭,數據能夠直接提交給硬件發送。sk_buff是被鎖住的(locked),
確保其餘程序不會存取它。

2.2.5 接收(reception)
    驅動程序並不存在一個接收方法。有數據收到應該是驅動程序來通知系統的。
通常設備收到數據後都會產生一箇中斷,在中斷處理程序中驅動程序申請一塊
sk_buff(skb),從硬件讀出數據放置到申請好的緩衝區裏。接下來填充sk_buff中
的一些信息。skb->dev = dev,判斷收到幀的協議類型,填入skb->protocol(多協
議的支持)。把指針skb->mac.raw指向硬件數據而後丟棄硬件幀頭(skb_pull)。還要
設置skb->pkt_type,標明第二層(鏈路層)數據類型。能夠是如下類型:
  PACKET_BROADCAST : 鏈路層廣播
  PACKET_MULTICAST : 鏈路層組播
  PACKET_SELF      : 發給本身的幀
  PACKET_OTHERHOST : 發給別人的幀(監聽模式時會有這種幀)

最後調用netif_rx()把數據傳送給協議層。netif_rx()裏數據放入處理隊列而後返
回,真正的處理是在中斷返回之後,這樣能夠減小中斷時間。調用netif_rx()之後,
驅動程序就不能再存取數據緩衝區skb。

2.2.6 硬件幀頭(hard_header)
    硬件通常都會在上層數據發送以前加上本身的硬件幀頭,好比以太網(Ethernet)
就有14字節的幀頭。這個幀頭是加在上層ip、ipx等數據包的前面的。驅動程序提供
一個hard_header方法,協議層(ip、ipx、arp等)在發送數據以前會調用這段程序。
硬件幀頭的長度必須填在dev->hard_header_len,這樣協議層回在數據以前保留好
硬件幀頭的空間。這樣hard_header程序只要調用skb_push而後正確填入硬件幀頭就
能夠了。
    在協議層調用hard_header時,傳送的參數包括(2.0.xx):數據的sk_buff,
device指針,protocol,目的地址(daddr),源地址(saddr),數據長度(len)。數據
長度不要使用sk_buff中的參數,由於調用hard_header時數據可能還沒徹底組織好。
saddr是NULL的話是使用缺省地址(default)。daddr是NULL代表協議層不知道硬件目
的地址。若是hard_header徹底填好了硬件幀頭,則返回添加的字節數。若是硬件幀
頭中的信息還不徹底(好比daddr爲NULL,可是幀頭中須要目的硬件地址。典型的情
況是以太網須要地址解析(arp)),則返回負字節數。hard_header返回負數的狀況
下,協議層會作進一步的build header的工做。目前Linux系統裏就是作arp
(若是hard_header返回正,dev->arp=1,代表不須要作arp,返回負,dev->arp=0,
作arp)。
    對hard_header的調用在每一個協議層的處理程序裏。如ip_output。

2.2.7 地址解析(xarp)
    有些網絡有硬件地址(好比Ethernet),而且在發送硬件幀時須要知道目的硬件
地址。這樣就須要上層協議地址(ip、ipx)和硬件地址的對應。這個對應是經過地址
解析完成的。須要作arp的的設備在發送以前會調用驅動程序的rebuild_header方
法。調用的主要參數包括指向硬件幀頭的指針,協議層地址。若是驅動程序可以解
析硬件地址,就返回1,若是不能,返回0。
    對rebuild_header的調用在net/core/dev.c的do_dev_queue_xmit()裏。

2.2.8 參數設置和統計數據
    在驅動程序裏還提供一些方法供系統對設備的參數進行設置和讀取信息。通常
只有超級用戶(root)權限才能對設備參數進行設置。設置方法有:
    dev->set_mac_address()
    當用戶調用ioctl類型爲SIOCSIFHWADDR時是要設置這個設備的mac地址。通常
對mac地址的設置沒有太大意義的。
    dev->set_config()

------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------

    當用戶調用ioctl時類型爲SIOCSIFMAP時,系統會調用驅動程序的set_config
方法。用戶會傳遞一個ifmap結構包含須要的I/O、中斷等參數。
    dev->do_ioctl()
    若是用戶調用ioctl時類型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之間,系統
會調用驅動程序的這個方法。通常是設置設備的專用數據。
    讀取信息也是經過ioctl調用進行。除次以外驅動程序還能夠提供一個
dev->get_stats方法,返回一個enet_statistics結構,包含發送接收的統計信息。
    ioctl的處理在net/core/dev.c的dev_ioctl()和dev_ifsioc()裏。


2.3 網絡驅動程序中用到的數據結構
    最重要的是網絡設備的數據結構。定義在include/linux/netdevice.h裏。它
的註釋已經足夠詳盡。
struct device
{

  /*
   * This is the first field of the "visible" part of this structure
   * (i.e. as seen by users in the "Space.c" file).  It is the name
   * the interface.
   */
  char                    *name;

  /* I/O specific fields - FIXME: Merge these and struct ifmap into one */
  unsigned long           rmem_end;             /* shmem "recv" end     */
  unsigned long           rmem_start;           /* shmem "recv" start   */
  unsigned long           mem_end;              /* shared mem end       */
  unsigned long           mem_start;            /* shared mem start     */
  unsigned long           base_addr;            /* device I/O address   */
  unsigned char           irq;                  /* device IRQ number    */

  /* Low-level status flags. */
  volatile unsigned char  start,                /* start an operation   */
                          interrupt;            /* interrupt arrived    */
  /* 在處理中斷時interrupt設爲1,處理完清0。 */
  unsigned long           tbusy;                /* transmitter busy must be longg for
bitops */

  struct device           *next;

  /* The device initialization function. Called only once. */
  /* 指向驅動程序的初始化方法。 */
  int                     (*init)(struct device *dev);

  /* Some hardware also needs these fields, but they are not part of the
     usual set specified in Space.c. */
  /* 一些硬件能夠在一塊板上支持多個接口,可能用到if_port。 */
  unsigned char           if_port;              /* Selectable AUI, TP,..*/
  unsigned char           dma;                  /* DMA channel          */

  struct enet_statistics* (*get_stats)(struct device *dev);

  /*
   * This marks the end of the "visible" part of the structure. All
   * fields hereafter are internal to the system, and may change at
   * will (read: may be cleaned up at will).
   */

  /* These may be needed for future network-power-down code. */
  /* trans_start記錄最後一次成功發送的時間。能夠用來肯定硬件是否工做正常。*/
  unsigned long           trans_start;  /* Time (in jiffies) of last Tx */
  unsigned long           last_rx;      /* Time of last Rx              */

  /* flags裏面有不少內容,定義在include/linux/if.h裏。*/
  unsigned short          flags;        /* interface flags (a la BSD)   */
  unsigned short          family;       /* address family ID (AF_INET)  */
  unsigned short          metric;       /* routing metric (not used)    */
  unsigned short          mtu;          /* interface MTU value          */

  /* type標明物理硬件的類型。主要說明硬件是否須要arp。定義在
     include/linux/if_arp.h裏。 */
  unsigned short          type;         /* interface hardware type      */

  /* 上層協議層根據hard_header_len在發送數據緩衝區前面預留硬件幀頭空間。*/
  unsigned short          hard_header_len;      /* hardware hdr length  */

  /* priv指向驅動程序本身定義的一些參數。*/
  void                    *priv;        /* pointer to private data      */

  /* Interface address info. */
  unsigned char           broadcast[MAX_ADDR_LEN];      /* hw bcast add */
  unsigned char           pad;                          /* make dev_addr alignedd to 8
bytes */
  unsigned char           dev_addr[MAX_ADDR_LEN];       /* hw address   */
  unsigned char           addr_len;     /* hardware address length      */
  unsigned long           pa_addr;      /* protocol address             */
  unsigned long           pa_brdaddr;   /* protocol broadcast addr      */
  unsigned long           pa_dstaddr;   /* protocol P-P other side addr */
  unsigned long           pa_mask;      /* protocol netmask             */
  unsigned short          pa_alen;      /* protocol address length      */

  struct dev_mc_list     *mc_list;      /* Multicast mac addresses      */
  int                    mc_count;      /* Number of installed mcasts   */

  struct ip_mc_list      *ip_mc_list;   /* IP multicast filter chain    */
  __u32                 tx_queue_len;   /* Max frames per queue allowed */


------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------

  /* For load balancing driver pair support */

  unsigned long            pkt_queue;   /* Packets queued */
  struct device           *slave;       /* Slave device */
  struct net_alias_info         *alias_info;    /* main dev alias info */
  struct net_alias              *my_alias;      /* alias devs */

  /* Pointer to the interface buffers. */
  struct sk_buff_head     buffs[DEV_NUMBUFFS];

  /* Pointers to interface service routines. */
  int                     (*open)(struct device *dev);
  int                     (*stop)(struct device *dev);
  int                     (*hard_start_xmit) (struct sk_buff *skb,
                                              struct device *dev);
  int                     (*hard_header) (struct sk_buff *skb,
                                          struct device *dev,
                                          unsigned short type,
                                          void *daddr,
                                          void *saddr,
                                          unsigned len);
  int                     (*rebuild_header)(void *eth, struct device *dev,
                                unsigned long raddr, struct sk_buff *skb);
#define HAVE_MULTICAST
  void                    (*set_multicast_list)(struct device *dev);
#define HAVE_SET_MAC_ADDR
  int                     (*set_mac_address)(struct device *dev, void *addr);
#define HAVE_PRIVATE_IOCTL
  int                     (*do_ioctl)(struct device *dev, struct ifreq *ifr, intt cmd);
#define HAVE_SET_CONFIG
  int                     (*set_config)(struct device *dev, struct ifmap *map);
#define HAVE_HEADER_CACHE
  void                    (*header_cache_bind)(struct hh_cache **hhp, struct devvice
*dev, unsigned short htype, __u32 daddr);
  void                    (*header_cache_update)(struct hh_cache *hh, struct devvice
*dev, unsigned char *  haddr);
#define HAVE_CHANGE_MTU
  int                     (*change_mtu)(struct device *dev, int new_mtu);

  struct iw_statistics*   (*get_wireless_stats)(struct device *dev);
};


2.4 經常使用的系統支持

2.4.1 內存申請和釋放
    include/linux/kernel.h裏聲明瞭kmalloc()和kfree()。用於在內核模式下申
請和釋放內存。
    void *kmalloc(unsigned int len,int priority);
    void kfree(void *__ptr);
    與用戶模式下的malloc()不一樣,kmalloc()申請空間有大小限制。長度是2的整
次方。能夠申請的最大長度也有限制。另外kmalloc()有priority參數,一般使用
時能夠爲GFP_KERNEL,若是在中斷裏調用用GFP_ATOMIC參數,由於使用GFP_KERNEL
則調用者可能進入sleep狀態,在處理中斷時是不容許的。
    kfree()釋放的內存必須是kmalloc()申請的。若是知道內存的大小,也能夠用
kfree_s()釋放。

2.4.2 request_irq()、free_irq()
    這是驅動程序申請中斷和釋放中斷的調用。在include/linux/sched.h裏聲明。
request_irq()調用的定義:
    int request_irq(unsigned int irq,
                 void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
                 unsigned long irqflags,
                 const char * devname,
                 void *dev_id);
    irq是要申請的硬件中斷號。在Intel平臺,範圍0--15。handler是向系統登記
的中斷處理函數。這是一個回調函數,中斷髮生時,系統調用這個函數,傳入的參
數包括硬件中斷號,device id,寄存器值。dev_id就是下面的request_irq時傳遞
給系統的參數dev_id。irqflags是中斷處理的一些屬性。比較重要的有SA_INTERRUPT,
標明中斷處理程序是快速處理程序(設置SA_INTERRUPT)仍是慢速處理程序(不設置
SA_INTERRUPT)。快速處理程序被調用時屏蔽全部中斷。慢速處理程序不屏蔽。還有
一個SA_SHIRQ屬性,設置了之後運行多個設備共享中斷。dev_id在中斷共享時會用
到。通常設置爲這個設備的device結構自己或者NULL。中斷處理程序能夠用dev_id
找到相應的控制這個中斷的設備,或者用irq2dev_map找到中斷對應的設備。
    void free_irq(unsigned int irq,void *dev_id);

2.4.3 時鐘
    時鐘的處理相似中斷,也是登記一個時間處理函數,在預約的時間事後,系統
會調用這個函數。在include/linux/timer.h裏聲明。
    struct timer_list {
          struct timer_list *next;
          struct timer_list *prev;
          unsigned long expires;
          unsigned long data;
          void (*function)(unsigned long);
    };
    void add_timer(struct timer_list * timer);
    int del_timer(struct timer_list * timer);
    void init_timer(struct timer_list * timer);
    使用時鐘,先聲明一個timer_list結構,調用init_timer對它進行初始化。
time_list結構裏expires是標明這個時鐘的週期,單位採用jiffies的單位。
jiffies是Linux一個全局變量,表明時間。它的單位隨硬件平臺的不一樣而不一樣。
系統裏定義了一個常數HZ,表明每秒種最小時間間隔的數目。這樣jiffies的單位
就是1/HZ。Intel平臺jiffies的單位是1/100秒,這就是系統所能分辨的最小時間
間隔了。因此expires/HZ就是以秒爲單位的這個時鐘的週期。
    function就是時間到了之後的回調函數,它的參數就是timer_list中的data。
data這個參數在初始化時鐘的時候賦值,通常賦給它設備的device結構指針。
    在預置時間到系統調用function,同時系統把這個time_list從定時隊列裏清
除。因此若是須要一直使用定時函數,要在function裏再次調用add_timer()把這

------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------

個timer_list加進定時隊列。

2.4.4 I/O
    I/O端口的存取使用:
    inline unsigned int inb(unsigned short port);
    inline unsigned int inb_p(unsigned short port);
    inline void outb(char value, unsigned short port);
    inline void outb_p(char value, unsigned short port);
    在include/adm/io.h裏定義。
    inb_p()、outb_p()與inb()、outb_p()的不一樣在於前者在存取I/O時有等待
(pause)一適應慢速的I/O設備。
    爲了防止存取I/O時發生衝突,Linux提供對端口使用狀況的控制。在使用端口
以前,能夠檢查須要的I/O是否正在被使用,若是沒有,則把端口標記爲正在使用,
使用完後再釋放。系統提供如下幾個函數作這些工做。
    int check_region(unsigned int from, unsigned int extent);
    void request_region(unsigned int from, unsigned int extent,const char *name)
    void release_region(unsigned int from, unsigned int extent);
    其中的參數from表示用到的I/O端口的起始地址,extent標明從from開始的端
口數目。name爲設備名稱。

2.4.5 中斷打開關閉
    系統提供給驅動程序開放和關閉響應中斷的能力。是在include/asm/system.h
中的兩個定義。
    #define cli() __asm__ __volatile__ ("cli"::)
    #define sti() __asm__ __volatile__ ("sti"::)

2.4.6 打印信息
    相似普通程序裏的printf(),驅動程序要輸出信息使用printk()。在include
/linux/kernel.h裏聲明。
    int printk(const char* fmt, ...);
    其中fmt是格式化字符串。...是參數。都是和printf()格式同樣的。

2.4.7 註冊驅動程序
    若是使用模塊(module)方式加載驅動程序,須要在模塊初始化時把設備註冊
到系統設備表裏去。再也不使用時,把設備從系統中卸除。定義在drivers/net/net_init.h
裏的兩個函數完成這個工做。
    int register_netdev(struct device *dev);
    void unregister_netdev(struct device *dev);
    dev就是要註冊進系統的設備結構指針。在register_netdev()時,dev結構一
般填寫前面11項,即到init,後面的暫時能夠不用初始化。最重要的是name指針和
init方法。name指針空(NULL)或者內容爲'/0'或者name[0]爲空格(space),則系統
把你的設備作爲以太網設備處理。以太網設備有統一的命名格式,ethX。對以太網
這麼特別對待大概和Linux的歷史有關。
    init方法必定要提供,register_netdev()會調用這個方法讓你對硬件檢測和
設置。
    register_netdev()返回0表示成功,非0不成功。

2.4.8 sk_buff
    Linux網絡各層之間的數據傳送都是經過sk_buff。sk_buff提供一套管理緩衝
區的方法,是Linux系統網絡高效運行的關鍵。每一個sk_buff包括一些控制方法和一
塊數據緩衝區。控制方法按功能分爲兩種類型。一種是控制整個buffer鏈的方法,
另外一種是控制數據緩衝區的方法。sk_buff組織成雙向鏈表的形式,根據網絡應用
的特色,對鏈表的操做主要是刪除鏈表頭的元素和添加到鏈表尾。sk_buff的控制
方法都很短小以儘可能減小系統負荷。(translated from article written by Alan
Cox)
經常使用的方法包括:
    .alloc_skb() 申請一個sk_buff並對它初始化。返回就是申請到的sk_buff。
    .dev_alloc_skb()相似alloc_skb,在申請好緩衝區後,保留16字節的幀頭空
     間。主要用在Ethernet驅動程序。
    .kfree_skb() 釋放一個sk_buff。
    .skb_clone() 複製一個sk_buff,但不復制數據部分。
    .skb_copy()徹底複製一個sk_buff。
    .skb_dequeue() 從一個sk_buff鏈表裏取出第一個元素。返回取出的sk_buff,
     若是鏈表空則返回NULL。這是經常使用的一個操做。
    .skb_queue_head() 在一個sk_buff鏈表頭放入一個元素。
    .skb_queue_tail() 在一個sk_buff鏈表尾放入一個元素。這也是經常使用的一個
     操做。網絡數據的處理主要是對一個先進先出隊列的管理,skb_queue_tail()
     和skb_dequeue()完成這個工做。
    .skb_insert() 在鏈表的某個元素前插入一個元素。
    .skb_append() 在鏈表的某個元素後插入一個元素。一些協議(如TCP)對沒按
     順序到達的數據進行重組時用到skb_insert()和skb_append()。

    .skb_reserve() 在一個申請好的sk_buff的緩衝區裏保留一塊空間。這個空間
     通常是用作下一層協議的頭空間的。
    .skb_put() 在一個申請好的sk_buff的緩衝區裏爲數據保留一塊空間。在
     alloc_skb之後,申請到的sk_buff的緩衝區都是處於空(free)狀態,有一個
     tail指針指向free空間,實際上開始時tail就指向緩衝區頭。skb_reserve()
     在free空間裏申請協議頭空間,skb_put()申請數據空間。見下面的圖。
    .skb_push() 把sk_buff緩衝區裏數據空間往前移。即把Head room中的空間移
     一部分到Data area。
    .skb_pull() 把sk_buff緩衝區裏Data area中的空間移一部分到Head room中。

            --------------------------------------------------
           |            Tail room(free)                       |
            --------------------------------------------------
                       After alloc_skb()

            --------------------------------------------------
           | Head room |        Tail room(free)               |
            --------------------------------------------------
                       After skb_reserve()

            --------------------------------------------------
           | Head room |     Data area     | Tail room(free)  |
            --------------------------------------------------
                       After skb_put()

            --------------------------------------------------
           |Head| skb_ |   Data            | Tail room(free)  |
           |room| push |                   |                  |
           |    |       Data area          |                  |
            --------------------------------------------------
                       After skb_push()

            --------------------------------------------------
           |   Head    | skb_ |  Data area | Tail room(free)  |
           |           | pull |            |                  |
           |    Head room     |            |                  |
            --------------------------------------------------
                       After skb_pull()


------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------


三.編寫Linux網絡驅動程序中須要注意的問題

3.1 中斷共享
    Linux系統運行幾個設備共享同一個中斷。須要共享的話,在申請的時候指明
共享方式。系統提供的request_irq()調用的定義:
    int request_irq(unsigned int irq,
                 void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
                 unsigned long irqflags,
                 const char * devname,
                 void *dev_id);
    若是共享中斷,irqflags設置SA_SHIRQ屬性,這樣就容許別的設備申請同一個
中斷。須要注意全部用到這個中斷的設備在調用request_irq()都必須設置這個屬
性。系統在回調每一箇中斷處理程序時,能夠用dev_id這個參數找到相應的設備。一
般dev_id就設爲device結構自己。系統處理共享中斷是用各自的dev_id參數依次調
用每個中斷處理程序。

3.2 硬件發送忙時的處理
    主CPU的處理能力通常比網絡發送要快,因此常常會遇到系統有數據要發,但
上一包數據網絡設備還沒發送完。由於在Linux裏網絡設備驅動程序通常不作數據
緩存,不能發送的數據都是通知系統發送不成功,因此必需要有一個機制在硬件不
忙時及時通知系統接着發送下面的數據。
    通常對發送忙的處理在前面設備的發送方法(hard_start_xmit)裏已經描述過,
即若是發送忙,置tbusy爲1。處理完髮送數據後,在發送結束中斷裏清tbusy,同
時用mark_bh()調用通知系統繼續發送。
    但在具體實現個人驅動程序時發現,這樣的處理系統好象並不能及時地知道硬
件已經空閒了,即在mark_bh()之後,系統要等一段時間纔會接着發送。形成發送
效率很低。2M線路只有10%不到的使用率。內核版本爲2.0.35。
    我最後的實現是不把tbusy置1,讓系統始終認爲硬件空閒,可是報告發送不成
功。系統會一直嘗試重發。這樣處理就運行正常了。可是遍循內核源碼中的網絡驅
動程序,彷佛沒有這樣處理的。不知道癥結在哪裏。

3.3 流量控制(flow control)
    網絡數據的發送和接收都須要流量控制。這些控制是在系統裏實現的,不須要
驅動程序作工做。每一個設備數據結構裏都有一個參數dev->tx_queue_len,這個參數
標明發送時最多緩存的數據包。在Linux系統裏以太網設備(10/100Mbps)
tx_queue_len通常設置爲100,串行線路(異步串口)爲10。實際上若是看源碼能夠
知道,設置了dev->tx_queue_len並非爲緩存這些數據申請了空間。這個參數只是
在收到協議層的數據包時判斷髮送隊列裏的數據是否是到了tx_queue_len的限度,
以決定這一包數據加不加進發送隊列。發送時另外一個方面的流控是更高層協議的發
送窗口(TCP協議裏就有發送窗口)。達到了窗口大小,高層協議就不會再發送數據。
    接收流控也分兩個層次。netif_rx()緩存的數據包有限制。另外高層協議也會
有一個最大的等待處理的數據量。

    發送和接收流控處理在net/core/dev.c的do_dev_queue_xmit()和netif_rx()
中。

3.4 調試
    不少Linux的驅動程序都是編譯進內核的,造成一個大的內核文件。但對調試
來講,這是至關麻煩的。調試驅動程序能夠用module方式加載。支持模塊方式的
驅動程序必須提供兩個函數:int init_module(void)和void cleanup_module(void)。
init_module()在加載此模塊時調用,在這個函數裏能夠register_netdev()註冊
設備。init_module()返回0表示成功,返回負表示失敗。cleanup_module()在驅動
程序被卸載時調用,清除佔用的資源,調用unregister_netdev()。
    模塊能夠動態地加載、卸載。在2.0.xx版本里,還有kerneld自動加載模塊,
可是2.2.xx中已經取消了kerneld。手工加載使用insmod命令,卸載用rmmod命令,
看內核中的模塊用lsmod命令。
    編譯驅動程序用gcc,主要命令行參數-DKERNEL -DMODULE。而且做爲模塊加載
的驅動程序,只編譯成obj形式(加-c參數)。編譯好的目標文件放在/lib/modules
/2.x.xx/misc下,在啓動文件裏用insmod加載。


四.進一步的閱讀
    Linux程序設計資料能夠從網上得到。這就是開放源代碼的好處。而且沒有什

麼「未公開的祕密」。我編寫驅動程序時參閱的主要資料包括:
    Linux內核源代碼
    <<The Linux Kernel Hacker's Guide>> by Michael K. Johnson
    <<Linux Kernel Module Programming Guide>> by Ori Pomerantz
    <<Linux下的設備驅動程序>> by olly in BBS水木清華站
    能夠選擇一個模板做爲開始,內核源代碼裏有一個網絡驅動程序的模板,
drivers/net/skeleton.c。裏面包含了驅動程序的基本內容。但這個模板是以以太
網設備爲對象的,以太網的處理在Linux系統裏有特殊「待遇」,因此若是不是以
太網設備,有些細節上要注意,主要在初始化程序裏。
    最後,多參照別人寫的程序,聽聽其餘開發者的經驗之談大概是最有效的幫助
了。

------------------ Linux操做系統網絡驅動程序編寫 -------------------
------------ Contact the author by mailto:bordi@bordi.dhs.org ------

--
m;33m※ 來源:·飲水思源站 bbs.sjtu.edu.cn·[FROM: 202.120.240.104]m

--

[目錄]


設備驅動(from hust)


一. linux設備概述
在概念上通常把設備分爲字符設備、塊設備。字符設備是指設備發送和接收數據以字符
形式的進行;而塊設備則以整個數據緩衝區的形式進行。可是,因爲網絡設備等有其特
殊性,實際上系統對它們單獨處理。
系統用主設備號(MAJOR)加次設備(MINOR)號來惟一標識一個設備。相同主設備號表
示同一類設備,例如都是硬盤;次設備號標識同類設備的個數。全部設備在適當的目錄
(一般在/dev目錄下)下必須有相應的文件,這樣字符設備和塊設備均可以經過文件操
做的系統調用了完成。不一樣的是,塊設備操做常常要和緩衝區打交道,更加複雜一點。
系統設備管理的整體框圖以下:
二. 主要數據結構
與設備管理有關的主要數據結構以下:
一、登記設備管理
系統對已登記設備的管理是由chrdevs和blkdevs這兩張列表來完成的:
/*src/fs/devices.c*/
struct device_struct {
const char * name;              //指向設備名稱
struct file_operations * fops;      //指向設備的訪問操做函數集,file_operati
ons定義在
include/linux/fs.h中
};
static  struct device_struct  chrdevs[MAX_CHRDEV] = {
{ NULL, NULL },
};                               //全部系統登記的字符設備列表
static  struct device_struct  blkdevs[MAX_BLKDEV] = {
{ NULL, NULL },
}                               //全部系統登記的塊設備列表
實際上這兩張列表的結構是同樣的,但在登記時每一個結構元素的值會不一樣(見初始化部
分)。Linux對設備的進行訪問時,訪問文件系統中相應的文件,經過文件系統和文件的
屬性描述塊,系統能夠找到該文件系統或文件對應設備的設備號。在實際訪問列表時,
以chrdevs[MAJOR][MINOR]或blkdevs[MAJOR][MINOR]形式訪問,相同主設備號(MAJOR)
的元素中fops的內容相同。文件系統中相關的的數據結構以下:
struct super_block {
kdev_t s_dev;             //該文件系統所在設備的設備標誌符

}                       //每一個文件系統對應一個super_block
struct inode {
kdev_t          i_dev;       //該文件所在設備的設備標誌符經過它能夠找到在設備列表中
…                  相應設備
}                      //每一個文件對應一個inode
二、I/O請求管理
系統會把一部分系統內存做爲塊設備驅動程序與文件系統接口之間的一層緩衝區,每一個
緩衝區與某臺塊設備中的特定區域相聯繫,文件系統首先試圖存在相應的緩衝區,如未
找到就向該設備發出I/O讀寫請求,由設備驅動程序對這些請求進行處理。所以,須要有
相應的數據結構進行管理。
/*src/include/linux/blkdev.h*/
struct blk_dev_struct {
void (*request_fn)(void);         //指向請求處理函數的指針,請求處理函數是寫
設備驅動
程序的重要一環,設備驅動程序在此函數中經過outb向位
於I/O空間中的設備命令寄存器發出命令
struct request * current_request;   //指向當前正在處理的請求,它和plug共同維
護了該設備
的請求隊列
struct request   plug;           //這是LINUX2.0版本與之前版本的一個不一樣之處,
plug
主要被用於異步提早讀寫操做,在這種狀況下,因爲沒有特別的請求,爲了提升系統性
能,須要等發送完全部的提早讀寫請求才開始進行請求處理,即unplug_device。
struct tq_struct plug_tq;         //設備對應的任務隊列
};
/*src/drivers/block/ll_rw_blk.c*/
struct  blk_dev_struct  blk_dev[MAX_BLKDEV];
其中每一個請求以request的類型的結構進行傳遞,定義以下:
/*src/include/linux/blk_dev.h*/
struct request {
volatile int rq_status;               //表示請求的狀態
kdev_t rq_dev;                    //是該請求對應的設備號,kdev_t是unsigned s
hort類型,高8位是主設備號,低8位是從設備號,每一請求都針對一個設備發出的;
        int cmd;                         //表示該請求對應的命令,取READ或WRITE;
        int errors;
        unsigned long sector;              //每一扇區的字節數
        unsigned long nr_sectors;           //每一扇區的扇區數
        unsigned long current_nr_sectors;    //當前的扇區數;
char * buffer;                    //存放buffer_head.b_data值,表示發出請求的
數據存取地址;
struct semaphore * sem;            //一個信號量,用來保證設備讀寫的原語操做,

sem=0時才能處理該請求;
        struct buffer_head * bh;            //讀寫緩衝區的頭指針
        struct buffer_head * bhtail;         //讀寫緩衝區的尾指針
        struct request * next;              //指向下一個請求
};
對不一樣塊設備的全部請求都放在請求數組all_requests中,該數組其實是一個請求緩
衝池,請求的釋放與獲取都是針對這個緩衝池進行;同時各個設備的請求用next指針聯
結起來,造成各自的請求隊列。定義以下:
/*src/drivers/blokc/ll_rw_blk.c*/
static struct request all_requests[NR_REQUEST];
三、中斷請求
設備進行實際的輸入/輸出操做時,若是時間過長而始終被佔用CPU,就會影響系統的效
率,必須有一種機制來克服這個問題而又不引發其餘問題。中斷是最理想的方法。和中
斷有關的數據結構是;
struct irqaction {
        void (*handler)(int, void *, struct pt_regs *);   //指向設備的中斷響應函數,
它在系統初
始化時被置入。當中斷髮生時,系統自動調用該函數
        unsigned long flags;                 //指示了中斷類型,如正常中斷、快速中斷

        unsigned long mask;                //中斷的屏蔽字
        const char *name;                  //設備名
        void *dev_id;                     //與設備相關的數據類型,中斷響應函數能夠根

據須要將它轉化所需的數據指針,從而達到訪問系統數據的功能
        struct irqaction *next;               //指向下一個irqaction
};
因爲中斷數目有限,且不多更新,因此係統在初始化時,從系統堆中分配內存給每個
irq_action指針,經過next指針將它們連成一個隊列。
四、高速緩衝區
爲了加速對物理設備的訪問速度,Linux將塊緩衝區放在Cache內,塊緩衝區是由buffer
_head連成的鏈表結構。buffer_head的數據結構以下:
/*include/linux/fs.h*/
struct buffer_head {
        unsigned long b_blocknr;                /* block number */
        kdev_t b_dev;                                   /* device (B_FREE = free) */
        kdev_t b_rdev;                                  /* Real device */
        unsigned long b_rsector;                        /* Real buffer location on disk */
        struct buffer_head * b_next;            /* Hash queue list */
        struct buffer_head * b_this_page;       /* circular list of buffers in one page *
/
        unsigned long b_state;                  /* buffer state bitmap (see above) */
        struct buffer_head * b_next_free;
        unsigned int b_count;                   /* users using this block */
        unsigned long b_size;                   /* block size */
        char * b_data;                                  /* pointer to data block (1024 bytes) */
        unsigned int b_list;                            /* List that this buffer appears */
        unsigned long b_flushtime;      /* Time when this (dirty) buffer should be
written */
        unsigned long b_lru_time;       /* Time when this buffer was last used. */
        struct wait_queue * b_wait;
        struct buffer_head * b_prev;            /* doubly linked list of hash-queue */
        struct buffer_head * b_prev_free;       /* doubly linked list of buffers */
        struct buffer_head * b_reqnext; /* request queue */
};
塊緩衝區主要由鏈表組成。空閒的buffer_head組成的鏈表是按塊大小的不一樣分類組成,
Linux目前支持塊大小爲5十二、102四、204八、4096和8192字節;第二部分是正在用的塊,
塊以Hash_table的形式組織,具備相同hash索引的緩衝塊連在一塊兒,hash索引根據設備
標誌符和該數據塊的塊號獲得;同時將同一狀態的緩衝區塊用LRU算法連在一塊兒。對緩衝
區的各個鏈表定義以下:
/* fs/buffer.c*/
static struct buffer_head ** hash_table;
static struct buffer_head * lru_list[NR_LIST] = {NULL, };
static struct buffer_head * free_list[NR_SIZES] = {NULL, };
static struct buffer_head * unused_list = NULL;
static struct buffer_head * reuse_list = NULL;
三. 設備的初始化
LINUX啓動時,完成了實模式下的系統初始化(arch/i386/boot/setup.S)與保護模式下
的核心初始化包括初始化寄存器和數據區(arch/i386/boot/compressed/head.S)、核心
代碼解壓縮、頁表初始化(arch/i386/kernel/head.S)、初始化idt、gdt和ldt等工做
後,系統轉入了核心。調用函數start_kernel啓動核心(init/main.c)後,將繼續各方
面的初始化工做,其中start_kernel最後將調用kernel_thread (init, NULL, 0),建立
init進程進行系統配置(其中包括全部設備的初始化工做)。
static int init(void * unused)
{
…………
/* 建立後臺進程bdflush,以不斷循環寫出文件系統緩衝區中"髒"的內容 */
kernel_thread(bdflush, NULL, 0);
/* 建立後臺進程kswapd,專門處理頁面換出工做  */
kswapd_setup();
kernel_thread(kswapd, NULL, 0);
…………
setup();
…………
在setup函數中,調用系統調用sys_setup()。sys_setup()的定義以下:
//fs/filesystems.c
asmlinkage int sys_setup(void)
{
        static int callable = 1;
… …
        if (!callable)
                return -1;
        callable = 0;
… …
        device_setup();
… …
在該系統調用中,靜態變量callable保證只被調用實際只一次,再次調用時後面的初始
化程序不執行。在該調用開始就先進行設備的初始化:device_setup()。
//dirvers/block/genhd.c
void device_setup(void)
{
        extern void console_map_init(void);
… …
        chr_dev_init();
        blk_dev_init();
… …
能夠看到device_setup()將依次執行chr_dev_init()、blk_dev_init()等各種設備的初
始化程序。每一個具體的init函數的內容和具體設備就有關了,可是它們都有一些必須完
成的任務:
一、 告訴內核這一驅動程序使用的主設備號,同時提供指向file_operation的指針,以
完成對chrdevs和blkdevs的初始化。
二、 對塊設備,須要將輸入/輸出處理程序的入口地址告訴內核。
三、 對塊設備,須要告訴緩衝區設備存取的數據塊的大小。
四. 設備管理的流程
下面咱們介紹一下整個設備管理的流程。咱們以塊設備爲例,字符設備的流程也和塊設
備相似,只是沒有請求隊列管理。
首先,文件系統經過調用ll_rw_block發出塊讀寫命令,讀寫請求管理層接到命令後,向
系統申請一塊讀寫請求緩衝區,在填寫完請求信息後,請求進入設備的讀寫請求隊列等
候處理。若是隊列是空的,則請求當即獲得處理,不然由系統負責任務調度,喚醒請求
處理。在請求處理過程當中,系統向I/O空間發出讀寫指令返回。當讀寫完畢後,經過中斷
通知系統,同時調用與設備相應的讀寫中斷響應函數。對設備的讀寫過程的流程能夠用
下圖表示。
五. 添加一個字符設備
做爲對linux設備管理的分析的總結,咱們介紹一下如何添加一個設備,首先介紹如何添
加一個字符設備。在後面的文章中,咱們將新添加的設備稱爲新設備,說明以咱們實現
的虛擬的字符設備爲例,步驟基本以下:
1. 肯定設備的設備名稱和主設備號:
咱們必須找一個尚未被使用的主設備號,分配給本身的字符設備。假設主設備號爲30
(在2.0.34的內核中尚未以30做爲主設備號的字符設備)。
2. 肯定編寫須要的file_operations中的操做函數,包括:
static int my_open(struct inode * inode,struct file * file)
//經過宏指令MINOR()提取inode參數的I_rdev字段,肯定輔助設備號,而後
檢查相應的讀寫忙標誌,看新設備是否已經打開。若是是,返回錯誤信息;
不然置讀寫忙標誌爲true,阻止再次打開新設備。
static void my_release(struct inode * inode,struct file * file)
//同my_open相似,只是置讀寫忙標誌爲false,容許再次打開新設備。
static int my _write(struct inode * inode,struct file * file,const char * bu
ffer,int count)
//用於對該設備的寫
static int my _read(struct inode * inode , struct file * file,char * buffer,
int count)
//用於對該設備的讀
static int my_ioctl(struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg)
                //用於傳送特殊的控制信息給設備驅動程序,或者長設備驅動程序取得狀態信息,
在咱們實現的虛擬字符設備中,這個函數的功能是用來打開和關閉跟蹤功
能。
3. 肯定編寫須要的初始化函數:
        void my_init(void)
//首先須要將上述的file_operations中的操做函數的地址賦給某個file_operations
的結構變量my_fops中的相應域;
而後調用標準內核函數登記該設備:register_chrdev(30,"mychd",&my_fops);
最後對必要的變量(例如讀寫忙標誌、跟蹤標誌等)賦初值。
4. 在drivers/char/mem.c中添加相應語句;
在chr_dev_init函數以前添加drgn_init的原型說明:
void my_init (void);
在chr_dev_init函數的return語句以前添加如下語句:
my_init ();    //用於在字符設備初始化時初始化新設備
5. 修改drivers/char/Makefile;
假設咱們把因此必要的函數寫mychd.c中,則找到"L_OBJS   := tty_io.o n_tty.o con
sole.o /"行,將"mychd.o"加到其中。
6. 將該設備私有的*.c,*.h複製到目錄drivers/char下。
7. 用命令:make clean;make dep;make zImage從新編譯內核。
8. 用mknod命令在目錄/dev下創建相應主設備號的用於讀寫的特殊文件。
完成了上述步驟,你在linux環境下編程時就能夠使用新設備了。
六. 添加一個塊設備
接下來咱們將介紹如何添加一個塊設備。在後面的文章中,咱們將新添加的塊設備稱爲
新設備,塊設備的添加過程和字符設備有類似之處,咱們將主要介紹其不一樣點,步驟基
本以下:
1. 肯定設備的設備名稱和主設備號
咱們必須找一個尚未被使用的主設備號,分配給本身的新設備。假設主設備號爲30(
在2.0.34的內核中尚未以30做爲主設備號的塊設備),則須要在include/linux/majo
r.h中加入以下句:
                        #define MY_MAJOR 30
這樣咱們能夠經過MY_MAJOR來肯定設備爲新設備,保證通用性。
2. 肯定編寫須要的file_operations中的操做函數:
static int my_open(struct inode * inode,struct file * file)
static void my_release(struct inode * inode,struct file * file)
static int my_ioctl(struct inode * inode, struct file * file,
unsigned int cmd, unsigned long arg)
因爲使用了高速緩存,塊設備驅動程序就不須要包含本身的read()、write()和fsync()
函數,但必須使用本身的open()、 release()和 ioctl()函數,這些函數的做用和字符
設備的相應函數相似。
3. 肯定編寫須要的輸入/輸出函數:
static int my _read(void)               //正確處理時返回值爲1,錯誤時返回值爲0
static int my _write(void)      //正確處理時返回值爲1,錯誤時返回值爲0
值得注意的是這兩個函數和字符設備中的my read()、mywrite()函數不一樣:
2 參數不一樣:字符設備中的函數是帶參數的,用於對其工做空間(也能夠當作是簡單的
緩衝區)中的必定長度(長度也是參數傳遞的)的字符進行讀寫;而塊設備的函數是沒
有參數的,它經過當前請求中的信息訪問高速緩存中的相應的塊,所以不須要參數輸入

2 調用形式不一樣:字符設備中的函數地址是存放在file_operations中的,在對字符設備
進行讀寫時就調用了;而塊設備的讀寫函數是在須要進行實際I/O時在request中調用。
這在後面能夠看到。
4. 肯定編寫須要的請求處理函數:
static void my_request(void)
在塊設備驅動程序中,不帶中斷服務子程序的請求處理函數是簡單的,典型的格式以下

static void my_request(void)
{
loop:
INIT_REQUEST;

        if (MINOR(CURRENT->dev)>MY_MINOR_MAX)
        {
                end_request(0);
                goto loop;
        }

        if (CURRENT ->cmd==READ)
//CUREENT是指向請求隊列頭的request結構指針
        {
                end_request(my_read());         //my_read()在前面已經定義
                goto loop;
        }

        if (CURRENT ->cmd==WRITE)
        {
                end_request(my_write());        //my_write()在前面已經定義
                goto loop;
        }

        end_request(0);
        goto loop;
}
實際上,一個真正的塊設備通常不可能沒有中斷服務子程序,另外設備驅動程序是在系
統調用中被調用的,這時由內核程序控制CPU,所以不能搶佔,只能自願放棄;所以驅動
程序必須調用sleep_on()函數,釋放對CPU的佔用;在中斷服務子程序將所需的數據複製
到內核內存後,再由它來發出wake_up()調用,
5. 若是須要,編寫中斷服務子程序
實際上,一個真正的塊設備通常不可能沒有中斷服務子程序,另外設備驅動程序是在系
統調用中被調用的,這時由內核程序控制CPU,所以不能搶佔,只能自願放棄;所以驅動
程序必須調用sleep_on()函數,釋放對CPU的佔用;在中斷服務子程序將所需的數據複製
到內核內存後,再由它來發出wake_up()調用。
另外兩段中斷服務子程序都要訪問和修改特定的內核數據結構時,必需要仔細協調,以
防止出現災難性的後果。
2 首先,在必要時能夠禁止中斷,這能夠經過sti()和cli()來容許和禁止中斷請求。
2 其次,修改特定的內核數據結構的程序段要儘量的短,使中斷不至於延時過長。
含有中斷服務子程序的塊設備驅動程序的編寫相對比較複雜,咱們尚未徹底實現,主
要問題是在中斷處理之間的協調。由於這些程序是要加入內核的,系統默認爲你是徹底
正確的,若是引發循環或者中斷長時間不響應,結果很是嚴重。咱們正在努力實現這一
程序。
6. 肯定編寫須要的初始化函數:
        void my_init(void)
須要將的file_operations中的操做函數的地址賦給某個file_operations的結構變量my
_fops中的相應域;一個典型的形式是:
                struct file_operations my_fops=
                {
                        0,
                        block_read,
                        block_write,
                        0,
                        0,
                        my_ioctl,
                        0,
                        my_open,
                        my_release,
                        block_fsync,
                        0,
                        0,
                        0,
                }
my_init中須要做的工做有:
2 首先調用標準內核函數登記該設備:
        register_chrdev(MY_MOJOR,"my--bdev",&my_fops);
2 將request()函數的地址告訴內核:
blk_dev[MY_MAJOR].request_fn=DEVICE_REQUEST;
DEVICE_REQUEST是請求處理函數的地址,它的定義將在稍後能夠看到。
2 告訴新設備的高速緩存的數據塊的塊大小:
my_block_size=512;      //也能夠是1024等等
blksize_size[MY_MAJOR]=& my_block_size;
爲了系統在初始化時可以對新設備進行初始化,須要在blk_dev_init()中添加一行代碼
,能夠插在blk_dev_init()中return 0的前面,格式爲:
        my_init();
7. 在include/linux/blk.h中添加相應語句;
到目前爲止,除了DEVICE_REQUEST符合外,尚未告訴內核到那裏去找你的request()函
數,爲此須要將一些宏定義加到blk.h中。在blk.h中找到相似的一行:
        #endif  /*MAJOR_NR==whatever    */
在這行前面加入以下宏定義:
        #elif (MAJOR_NR==whatever)

static void my_request(void);
#define DEVICE_NAME "MY_BLK_DEV"                //驅動程序名稱
#define DEVICE_REQUEST  my_request              //request()函數指針
#define DEVIEC_NR(device) (MINOR(device))       //計算實際設備號
#define DEVIEC_ON(device)                                       //用於須要打開的設備
#define DEVIEC_OFF(device)                              //用於須要關閉的設備
8. 修改drivers/block/Makefile;
假設咱們把因此必要的函數寫mybd.c中,則找到"L_OBJS   := tty_io.o n_tty.o cons
ole.o /"行,將"mybd.o"加到其中。
9. 將該設備私有的*.c,*.h複製到目錄drivers/block下。
10. 用命令:make clean;make dep;make zImage從新編譯內核。
11. 用mknod命令在目錄/dev下創建相應主設備號的用於讀寫的特殊文件。
完成了上述步驟,你在linux環境下編程時就能夠使用新設備了。
七. 一個虛擬的字符設備驅動程序
如下是一個虛擬的字符設備驅動程序,該程序是我和潘剛同窗的試驗結果,原本咱們還
打算寫一個虛擬的塊設備驅動程序,因爲時間關係,沒有可以徹底明白中斷中斷服務子
程序的編寫方法,所以沒有沒有可以實現一個能夠全部的虛擬的塊設備,很是遺憾。不
過主要步驟已經在上文中進行了介紹,我想再有一段時間應該可以完成,到時候必定交
給李老師看一下。
虛擬的字符設備驅動程序以下,在潘剛同窗的試驗報告中也有介紹:
/* drgn.h */
#ifdef  KERNEL
#define TRACE_TXT(text) {if(drgn_trace) {console_print(text);console_print("
/n");}}
#define TRACE_CHR(chr) {if(drgn_trace) console_print(chr);}
#define DRGN_READ  1
#define DRGN_WRITE 0
#endif
#define FALSE 0
#define TRUE  1
#define MAX_BUF   120
#define DRGN_TRON  (('M' << 8)|0x01)
#define DRGN_TROFF (('M' << 8)|0x02)
struct drgn_buf
{
   int buf_size;
   char buffer[MAX_BUF];
   struct drgn_buf *link;
};
/* drgn.c */
#define KERNEL
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/malloc.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <asm/irq.h>
#include "drgn.h"
static int drgn_trace;
static int write_busy;
static int read_busy;
static struct drgn_buf * qhead;
static struct drgn_buf * qtail;
static int  drgn_read(struct inode * , struct file * , char * , int );
static int  drgn_write(struct inode * , struct file * , const char *, int );

static int  drgn_ioctl(struct inode * , struct file * , unsigned int , unsig
ned long );
static int  drgn_open(struct inode *,struct file *);
static void drgn_release(struct inode *,struct file *);
/* extern void console_print(char *);*/
struct file_operations drgn_fops=
{
   NULL,
   drgn_read,
   drgn_write,
   NULL,
   NULL,
   drgn_ioctl,
   NULL,
   drgn_open,
   drgn_release,
   NULL,
   NULL,
   NULL,
   NULL
};
void drgn_init(void)
{
   drgn_trace=TRUE;
   if(register_chrdev(30,"drgn",&drgn_fops))
      TRACE_TXT("Cannot register drgn driver as major device 30.")
   else
      TRACE_TXT("Tiny devie driver registered successfully.")
   qhead=0;
   write_busy=FALSE;
   read_busy=FALSE;
/*  drgn_trace=FALSE;*/
   return;
}
static int drgn_open(struct inode * inode,struct file * file)
{
   TRACE_TXT("drgn_open")
   switch (MINOR(inode->i_rdev))
   {
      case DRGN_WRITE:
         if(write_busy)
            return -EBUSY;
         else{
            write_busy=TRUE;
            return 0;
         }
      case DRGN_READ:
         if(read_busy)
            return -EBUSY;
         else{
            read_busy=TRUE;
            return 0;
         }
      default:
         return -ENXIO;
   }
}
static void drgn_release(struct inode * inode,struct file * file)
{
   TRACE_TXT("drgn_release")
   switch (MINOR(inode->i_rdev))
   {
      case DRGN_WRITE:
         write_busy=FALSE;
         return;
      case DRGN_READ:
         read_busy=FALSE;
         return;
   }
}
static int drgn_write(struct inode * inode,struct file * file,
                        const char * buffer,int count)
{
   int i,len;
   struct drgn_buf * ptr;
   TRACE_TXT("drgn_write")
   if (MINOR(inode->i_rdev)!=DRGN_WRITE)
      return -EINVAL;
   if ((ptr=kmalloc(sizeof(struct drgn_buf),GFP_KERNEL))==0)
      return -ENOMEM;
   len=count < MAX_BUF?count:MAX_BUF;
   if (verify_area(VERIFY_READ,buffer,len))
      return -EFAULT;
   for(i=0;i < count && i<MAX_BUF;++i)
   {
      ptr->buffer[i]=(char) get_user((char*)(buffer+i));
      TRACE_CHR("w")
   }
   ptr->link=0;
   if(qhead==0)
      qhead=ptr;
   else
      qtail->link=ptr;
   qtail=ptr;
   TRACE_CHR("/n")
   ptr->buf_size=i;
   return i;
}
static int drgn_read(struct inode * inode , struct file * file,
                         char * buffer, int count)
{
   int i,len;
   struct drgn_buf * ptr;
   TRACE_TXT("drgn_read")
   if(MINOR(inode->i_rdev)!=DRGN_READ)
      return -EINVAL;
   if (qhead==0)
      return -ENODATA;
   ptr=qhead;
   qhead=qhead->link;
   len=count < ptr->buf_size?count:ptr->buf_size;
   if (verify_area(VERIFY_WRITE,buffer,len))
      return -EFAULT;
   for (i=0; i<count && i<ptr->buf_size; ++i)
   {
      put_user((char) ptr->buffer[i],(char *)(buffer+i));
      TRACE_CHR("r")
   }
   TRACE_CHR("/n")
   kfree_s(ptr,sizeof(struct drgn_buf));
   return i;
}
static int drgn_ioctl(struct inode * inode, struct file * file,
                         unsigned int cmd, unsigned long arg)
{
   TRACE_TXT("drgn_ioctl")
/*   if (cmd==DRGN_TRON){
       drgn_trace=TRUE;
       return 0;
   }
   else
       if (cmd==DRGN_TROFF){
           drgn_trace=FALSE;
           return 0;
       }
       else
           return -EINVAL;*/
   switch(cmd)
   {
      case DRGN_TRON:
         drgn_trace=TRUE;
         return 0;
      case DRGN_TROFF:
         drgn_trace=FALSE;
         return 0;
      default:
         return -EINVAL;
   }
}

[目錄]


設備驅動(from smth)


1.        UNIX下設備驅動程序的基本結構
在UNIX系統裏,對用戶程序而言,設備驅動程序隱藏了設備的具體細節,對各類不一樣設備提供了一致的接口,通常來講是把設備映射爲一個特殊的設備文件,用戶程序能夠象對其它文件同樣對此設備文件進行操做。UNIX對硬件設備支持兩個標準接口:塊特別設備文件和字符特別設備文件,經過塊(字符)特別 設備文件存取的設備稱爲塊(字符)設備或具備塊(字符)設備接口。 塊設備接口僅支持面向塊的I/O操做,全部I/O操做都經過在內核地址空間中的I/O緩衝區進行,它能夠支持幾乎任意長度和任意位置上的I/O請求,即提供隨機存取的功能。
字符設備接口支持面向字符的I/O操做,它不通過系統的快速緩存,因此它們負責管理本身的緩衝區結構。字符設備接口只支持順序存取的功能,通常不能進行任意長度的I/O請求,而是限制I/O請求的長度必須是設備要求的基本塊長的倍數。顯然,本程序所驅動的串行卡只能提供順序存取的功能,屬因而字符設備,所以後面的討論在兩種設備有所區別時都只涉及字符型設備接口。設備由一個主設備號和一個次設備號標識。主設備號惟一標識了設備類型,即設備驅動程序類型,它是塊設備表或字符設備表中設備表項的索引。次設備號僅由設備驅動程序解釋,通常用於識別在若干可能的硬件設備中,I/O請求所涉及到的那個設備。
設備驅動程序能夠分爲三個主要組成部分:
(1) 自動配置和初始化子程序,負責檢測所要驅動的硬件設備是否存在和是否能正常工做。若是該設備正常,則對這個設備及其相關的、設備驅動程序須要的軟件狀態進行初始化。這部分驅動程序僅在初始化的時候被調用一次。
(2) 服務於I/O請求的子程序,又稱爲驅動程序的上半部分。調用這部分是因爲系統調用的結果。這部分程序在執行的時候,系統仍認爲是和進行調用的進程屬於同一個進程,只是由用戶態變成了核心態,具備進行此係統調用的用戶程序的運行環境,所以能夠在其中調用sleep()等與進程運行環境有關的函數。
(3) 中斷服務子程序,又稱爲驅動程序的下半部分。在UNIX系統中,並非直接從中斷向量表中調用設備驅動程序的中斷服務子程序,而是由UNIX系統來接收硬件中斷,再由系統調用中斷服務子程序。中斷能夠產生在任何一個進程運行的時候,所以在中斷服務程序被調用的時候,不能依賴於任何進程的狀態,也就不能調用任何與進程運行環境有關的函數。由於設備驅動程序通常支持同一類型的若干設備,因此通常在系統調用中斷服務子程序的時候,都帶有一個或多個參數,以惟一標識請求服務的設備。
在系統內部,I/O設備的存取經過一組固定的入口點來進行,這組入口點是由每一個設備的設備驅動程序提供的。通常來講,字符型設備驅動程序可以提供以下幾個入口點:
(1) open入口點。打開設備準備I/O操做。對字符特別設備文件進行打開操做,都會調用設備的open入口點。open子程序必須對將要進行的I/O操做作好必要的準備工做,如清除緩衝區等。若是設備是獨佔的,即同一時刻只能有一個程序訪問此設備,則open子程序必須設置一些標誌以表示設備處於忙狀態。
(2) close入口點。關閉一個設備。當最後一次使用設備終結後,調用close子程序。獨佔設備必須標記設備可再次使用。
(3) read入口點。從設備上讀數據。對於有緩衝區的I/O操做,通常是從緩衝區裏讀數據。對字符特別設備文件進行讀操做將調用read子程序。
(4) write入口點。往設備上寫數據。對於有緩衝區的I/O操做,通常是把數據寫入緩衝區裏。對字符特別設備文件進行寫操做將調用write子程序。
(5) ioctl入口點。執行讀、寫以外的操做。
(6) select入口點。檢查設備,看數據是否可讀或設備是否可用於寫數據。select系統調用在檢查與設備特別文件相關的文件描述符時使用select入口點。若是設備驅動程序沒有提供上述入口點中的某一個,系統會用缺省的子程序來代替。對於不一樣的系統,也還有一些其它的入口點。

2.        LINUX系統下的設備驅動程序
具體到LINUX系統裏,設備驅動程序所提供的這組入口點由一個結構來向系
統進行說明,此結構定義爲:

#include <linux/fs.h>
struct file_operations {
        int (*lseek)(struct inode *inode,struct file *filp,
                off_t off,int pos);
        int (*read)(struct inode *inode,struct file *filp,
                char *buf, int count);
        int (*write)(struct inode *inode,struct file *filp,
                char *buf,int count);
        int (*readdir)(struct inode *inode,struct file *filp,
                struct dirent *dirent,int count);
        int (*select)(struct inode *inode,struct file *filp,
                int sel_type,select_table *wait);
        int (*ioctl) (struct inode *inode,struct file *filp,
                unsigned int cmd,unsigned int arg);
        int (*mmap) (void);

        int (*open) (struct inode *inode, struct file *filp);
        void (*release) (struct inode *inode, struct file *filp);
        int (*fsync) (struct inode *inode, struct file *filp);
};

其中,struct inode提供了關於特別設備文件/dev/driver(假設此設備名爲driver)的信息,它的定義爲:

#include <linux/fs.h>
struct inode {
        dev_t           i_dev;
        unsigned long    i_ino;  /* Inode number */
        umode_t        i_mode; /* Mode of the file */
        nlink_t          i_nlink;
        uid_t           i_uid;
        gid_t           i_gid;
        dev_t           i_rdev;  /* Device major and minor numbers*/
        off_t            i_size;
        time_t          i_atime;
        time_t          i_mtime;
        time_t          i_ctime;
        unsigned long   i_blksize;
        unsigned long   i_blocks;
        struct inode_operations * i_op;
      struct super_block * i_sb;
        struct wait_queue * i_wait;
        struct file_lock * i_flock;
        struct vm_area_struct * i_mmap;
        struct inode * i_next, * i_prev;
        struct inode * i_hash_next, * i_hash_prev;
        struct inode * i_bound_to, * i_bound_by;
        unsigned short i_count;
        unsigned short i_flags;  /* Mount flags (see fs.h) */
        unsigned char i_lock;
        unsigned char i_dirt;
        unsigned char i_pipe;
        unsigned char i_mount;
        unsigned char i_seek;
        unsigned char i_update;
        union {
                struct pipe_inode_info pipe_i;
                struct minix_inode_info minix_i;
                struct ext_inode_info ext_i;
                struct msdos_inode_info msdos_i;
                struct iso_inode_info isofs_i;
                struct nfs_inode_info nfs_i;
        } u;
};

struct file主要用於與文件系統對應的設備驅動程序使用。固然,其它設備驅動程序也能夠使用它。它提供關於被打開的文件的信息,定義爲:#include <linux/fs.h>
struct file {
        mode_t f_mode;
        dev_t f_rdev;             /* needed for /dev/tty */
        off_t f_pos;              /* Curr. posn in file */
        unsigned short f_flags;   /* The flags arg passed to open */
        unsigned short f_count;   /* Number of opens on this file */
        unsigned short f_reada;
        struct inode *f_inode;    /* pointer to the inode struct */
        struct file_operations *f_op;/* pointer to the fops struct*/
};

在結構file_operations裏,指出了設備驅動程序所提供的入口點位置,分別是
(1) lseek,移動文件指針的位置,顯然只能用於能夠隨機存取的設備。
(2) read,進行讀操做,參數buf爲存放讀取結果的緩衝區,count爲所要讀取的數據長度。返回值爲負表示讀取操做發生錯誤,不然返回實際讀取的字節數。對於字符型,要求讀取的字節數和返回的實際讀取字節數都必須是inode->i_blksize的的倍數。
(3) write,進行寫操做,與read相似。
(4) readdir,取得下一個目錄入口點,只有與文件系統相關的設備驅動程序才使用。
(5) selec,進行選擇操做,若是驅動程序沒有提供select入口,select操做將會認爲設備已經準備好進行任何的I/O操做。
(6) ioctl,進行讀、寫之外的其它操做,參數cmd爲自定義的的命令。
(7) mmap,用於把設備的內容映射到地址空間,通常只有塊設備驅動程序使用。
(8) open,打開設備準備進行I/O操做。返回0表示打開成功,返回負數表示失敗。若是驅動程序沒有提供open入口,則只要/dev/driver文件存在就認爲打開成功。
(9) release,即close操做。
設備驅動程序所提供的入口點,在設備驅動程序初始化的時候向系統進行登記,以便系統在適當的時候調用。LINUX系統裏,經過調用register_chrdev向系統註冊字符型設備驅動程序。register_chrdev定義爲:

#include <linux/fs.h>
#include <linux/errno.h>
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

其中,major是爲設備驅動程序向系統申請的主設備號,若是爲0則系統爲此驅動程序動態地分配一個主設備號。name是設備名。fops就是前面所說的對各個調用的入口點的說明。此函數返回0表示成功。返回-EINVAL表示申請的主設備號非法,通常來講是主設備號大於系統所容許的最大設備號。返回-EBUSY表示所申請的主設備號正在被其它設備驅動程序使用。若是是動態分配主設備號成功,此函數將返回所分配的主設備號。若是register_chrdev操做成功,設備名就會出如今/proc/devices文件裏。
初始化部分通常還負責給設備驅動程序申請系統資源,包括內存、中斷、時鐘、I/O端口等,這些資源也能夠在open子程序或別的地方申請。在這些資源不用的時候,應該釋放它們,以利於資源的共享。在UNIX系統裏,對中斷的處理是屬於系統核心的部分,所以若是設備與系統之間以中斷方式進行數據交換的話,就必須把該設備的驅動程序做爲系統核心
的一部分。設備驅動程序經過調用request_irq函數來申請中斷,經過free_irq來釋放中斷。它們的定義爲:

#include <linux/sched.h>
int request_irq(unsigned int irq,
            void (*handler)(int irq,void dev_id,struct pt_regs *regs),
            unsigned long flags,
            const char *device,
            void *dev_id);
void free_irq(unsigned int irq, void *dev_id);

參數irq表示所要申請的硬件中斷號。handler爲向系統登記的中斷處理子程序,中斷產生時由系統來調用,調用時所帶參數irq爲中斷號,dev_id爲申請時告訴系統的設備標識,regs爲中斷髮生時寄存器內容。device爲設備名,將會出如今/proc/interrupts文件裏。flag是申請時的選項,它決定中斷處理程序的一些特性,其中最重要的是中斷處理程序是快速處理程序(flag裏設置了SA_INTERRUPT)仍是慢速處理程序(不設置SA_INTERRUPT),快速處理程序運行時,全部中斷都被屏蔽,而慢速處理程序運行時,除了正在處理的中斷外,其它中斷都沒有被屏蔽。
在LINUX系統中,中斷能夠被不一樣的中斷處理程序共享,這要求每個共享此中斷的處理程序在申請中斷時在flags裏設置SA_SHIRQ,這些處理程序之間以dev_id來區分。若是中斷由某個處理程序獨佔,則dev_id能夠爲NULL。request_irq返回0表示成功,返回-INVAL表示irq>15或handler==NULL,返回-EBUSY表示中斷已經被佔用且不能共享。做爲系統核心的一部分,設備驅動程序在申請和釋放內存時不是調用malloc和free,而代之以調用kmalloc和kfree,它們被定義爲:

#include <linux/kernel.h>
void * kmalloc(unsigned int len, int priority);
void kfree(void * obj);

參數len爲但願申請的字節數,obj爲要釋放的內存指針。priority爲分配內存操做的優先級,即在沒有足夠空閒內存時如何操做,通常用GFP_KERNEL。與中斷和內存不一樣,使用一個沒有申請的I/O端口不會使CPU產生異常,也就不會致使諸如「segmentation fault"一類的錯誤發生。任何進程均可以訪問任何一個I/O端口。此時系統沒法保證對I/O端口的操做不會發生衝突,甚至會所以而使系統崩潰。所以,在使用I/O端口前,也應該檢查此I/O端口是否已有別的程序在使用,若沒有,再把此端口標記爲正在使用,在使用完之後釋放它。這樣須要用到以下幾個函數:

int check_region(unsigned int from, unsigned int extent);
void request_region(unsigned int from, unsigned int extent, const char *name);
void release_region(unsigned int from, unsigned int extent);

調用這些函數時的參數爲:from表示所申請的I/O端口的起始地址;extent爲所要申請的從from開始的端口數;name爲設備名,將會出如今/proc/ioports文件裏。check_region返回0表示I/O端口空閒,不然爲正在被使用。
在申請了I/O端口以後,就能夠以下幾個函數來訪問I/O端口:

#include <asm/io.h>
inline unsigned int inb(unsigned short port);
inline unsigned int inb_p(unsigned short port);
inline void outb(char value, unsigned short port);
inline void outb_p(char value, unsigned short port);

其中inb_p和outb_p插入了必定的延時以適應某些慢的I/O端口。在設備驅動程序裏,通常都須要用到計時機制。在LINUX系統中,時鐘是由系統接管,設備驅動程序能夠向系統申請時鐘。與時鐘有關的系統調用有:

#include <asm/param.h>
#include <linux/timer.h>
void add_timer(struct timer_list * timer);
int  del_timer(struct timer_list * timer);
inline void init_timer(struct timer_list * timer);

struct timer_list的定義爲:

struct timer_list {
               struct timer_list *next;
               struct timer_list *prev;
               unsigned long expires;
               unsigned long data;
               void (*function)(unsigned long d);
       };

其中expires是要執行function的時間。系統核心有一個全局變量JIFFIES表示當前時間,通常在調用add_timer時jiffies=JIFFIES+num,表示在num個系統最小時間間隔後執行function。系統最小時間間隔與所用的硬件平臺有關,在覈內心定義了常數HZ表示一秒內最小時間間隔的數目,則num*HZ表示num秒。系統計時到預約時間就調用function,並把此子程序從定時隊列裏刪除,所以若是想要每隔必定時間間隔執行一次的話,就必須在function裏再一次調用add_timer。function的參數d即爲timer裏面的data項。在設備驅動程序裏,還可能會用到以下的一些系統函數:

#include <asm/system.h>
#define cli() __asm__ __volatile__ ("cli"::)
#define sti() __asm__ __volatile__ ("sti"::)

這兩個函數負責打開和關閉中斷容許。

#include <asm/segment.h>
void memcpy_fromfs(void * to,const void * from,unsigned long n);
void memcpy_tofs(void * to,const void * from,unsigned long n);

在用戶程序調用read 、write時,由於進程的運行狀態由用戶態變爲核心態,地址空間也變爲核心地址空間。而read、write中參數buf是指向用戶程序的私有地址空間的,因此不能直接訪問,必須經過上述兩個系統函數來訪問用戶程序的私有地址空間。memcpy_fromfs由用戶程序地址空間往核心地址空間複製,memcpy_tofs則反之。參數to爲複製的目的指針,from爲源指針,n爲要複製的字節數。在設備驅動程序裏,能夠調用printk來打印一些調試信息,用法與printf相似。printk打印的信息不只出如今屏幕上,同時還記錄在文件syslog裏。

3.        LINUX系統下的具體實現
在LINUX裏,除了直接修改系統核心的源代碼,把設備驅動程序加進核內心之外,還能夠把設備驅動程序做爲可加載的模塊,由系統管理員動態地加載它,使之成爲核心地一部分。也能夠由系統管理員把已加載地模塊動態地卸載下來。
LINUX中,模塊能夠用C語言編寫,用gcc編譯成目標文件(不進行連接,做爲*.o文件存在),爲此須要在gcc命令行里加上-c的參數。在編譯時,還應該在gcc的命令行里加上這樣的參數:-D__KERNEL__ -DMODULE。因爲在不連接時,gcc只容許一個輸入文件,所以一個模塊的全部部分都必須在一個文件裏實現。編譯好的模塊*.o放在/lib/modules/xxxx/misc下(xxxx表示核心版本,如在覈心版本爲2.0.30時應該爲/lib/modules/2.0.30/misc),而後用depmod -a使此模塊成爲可加載模塊。模塊用insmod命令加載,用rmmod命令來卸載,並能夠用lsmod命令來查看全部已加載的模塊的狀態。
編寫模塊程序的時候,必須提供兩個函數,一個是int init_module(void),供insmod在加載此模塊的時候自動調用,負責進行設備驅動程序的初始化工做。init_module返回0以表示初始化成功,返回負數表示失敗。另外一個函數是voidcleanup_module (void),在模塊被卸載時調用,負責進行設備驅動程序的清除工做。
在成功的向系統註冊了設備驅動程序後(調用register_chrdev成功後),就能夠用mknod命令來把設備映射爲一個特別文件,其它程序使用這個設備的時候,只要對此特別文件進行操做就好了。

 

[目錄]


數據接口卡


發信人: jecky (還年輕,日子長呢), 信區: KernelTech

我在作一塊數據接口卡的驅動;
在open()函數裏我分配了內核的一段內存做爲緩衝區;
在release()裏釋放;
數據到達接口卡後,會 產生中斷;
我在中斷程序裏面將數據存在緩衝區內;
在read()時讀出數據緩衝區的數據。

在調試接收中斷時,我遇到了問題;
我是在open函數的最後加了sti()函數;release裏cli()
但在打印的內核信息中我看到進入中斷函數是在調用release的後面;
這時我已經將分配的內存釋放,從而出錯;
我不明白爲何中斷函數會在device close之後調用,
如何解決,高手請指教。

============================================================
發信人: mephisto (夢菲斯特:餅乾), 信區: KernelTech

  沒有註銷掉中斷處理程序

===========================================================
發信人: jecky (還年輕,日子長呢), 信區: KernelTech

你是指的freeirq麼?
請解釋清楚一點能夠麼,
我是新手。

==========================================================
發信人: mephisto (夢菲斯特:餅乾), 信區: KernelTech

【 在 jecky (還年輕,日子長呢) 的大做中提到: 】
: 我在作一塊數據接口卡的驅動;
: 在open()函數裏我分配了內核的一段內存做爲緩衝區;
: 在release()裏釋放;
: 數據到達接口卡後,會 產生中斷;
: 我在中斷程序裏面將數據存在緩衝區內;
: 在read()時讀出數據緩衝區的數據。
: 在調試接收中斷時,我遇到了問題;
: 我是在open函數的最後加了sti()函數;release裏cli()
中斷平時就是開着的,您不必手動的打開,固然也不必手動的關閉。
確實須要原子操做的程序段,能夠這樣:
save_flags(flags)
cli()
...
..
restore_flags(flags)

===============================================================
發信人: mephisto (夢菲斯特:餅乾), 信區: KernelTech


那個cli() sti()和DOS編程是不同的.
不要期望cli()之後的影響是全局的.確定是何時被系統改了,
並且這樣是不對的,應該close就把divice handler 懈調,若是你不想進入你的
handler的話.

 

[目錄]


代碼

 

[目錄]


標準範例

 

[目錄]


header.c


/***********************************************************************
    Case study source code from the book `The Linux A to Z'
    by Phil Cornes. Published by Prentice Hall, 1996.
    Copyright (C) 1996 Phil Cornes

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***********************************************************************/

/* 1 */
#define KERNEL

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/malloc.h>

#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <asm/irq.h>

#include "tdd.h"

/* 2 */
static int tdd_trace;
static int write_busy;
static int read_busy;
static struct tdd_buf *qhead;
static struct tdd_buf *qtail;

/* 3 */
static int tdd_read(struct inode *, struct file *, char *, int);
static int tdd_write(struct inode *, struct file *, char *, int);
static int tdd_ioctl(struct inode *, struct file *, unsigned int,
unsigned long);
static int tdd_open(struct inode *, struct file *);
static void tdd_release(struct inode *, struct file *);
extern void console_print(char *);

struct file_operations tdd_fops =
{
    NULL,
    tdd_read,
    tdd_write,
    NULL,
    NULL,
    tdd_ioctl,
    NULL,
    tdd_open,
    tdd_release,
    NULL,
    NULL,
    NULL,
    NULL
};

[目錄]


init.c


/***********************************************************************
    Case study source code from the book `The Linux A to Z'
    by Phil Cornes. Published by Prentice Hall, 1996.
    Copyright (C) 1996 Phil Cornes

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You sh

相關文章
相關標籤/搜索