bh處理
1.三種舊式的bottom half 處理類型
IMMEDIATE_BH: driver注冊入tq_immediate隊列,等待調度
TQUEUE_BH: 執行tq_timer,到系統tick產生時觸發
TIMER_BH: 直接綁定到do_timer()處理函數
2.mark_bh()
將激活32個BH中的某一個,觸發軟中斷來進入被調度狀態
此種軟中斷優先級為 HI_SOFTIRQ
3.tasklet
此方法將觸發軟中斷,使tasklet t被進入調度狀態
此方式觸發的軟中斷優先級為TASKLET_SOFTIRQ
使用方式:
1.
聲明tasklet:
DECLARE_TASKLET
2.
執行tasklet
tasklet_schedule(t)
init_bh(TIMER_BH,
timer_bh); 定時器中斷將執行 timer_bh任務隊列
init_bh(TQUEUE_BH,
tqueue_bh);
init_bh(IMMEDIATE_BH,
immediate_bh);
BH后半部處理類型
interrupts.h
enum {
TIMER_BH
= 0, /*時鐘*/
TQUEUE_BH, 定時器隊列
DIGI_BH,
SERIAL_BH,
RISCOM8_BH,
SPECIALIX_BH,
AURORA_BH,
ESP_BH,
SCSI_BH,
IMMEDIATE_BH,
CYCLADES_BH,
CM206_BH,
JS_BH,
MACSERIAL_BH,
ISICOM_BH
};
bonfs
1.bonfs 是一種簡易的只讀文件系統,在2440項目中,bonfs是一個可選的塊設備。
bonfs是一種設備,他位于mtd管理層的上端,所以其可以 register_mtd_user()注冊一個mtd_user.
此刻如果已經存在mtd設備或者mtd分區設備,在register_mtd_user()時,每個mtd設備都會通過 notifier_add()通知bonfs,然后bonfs便可以進行初始化操作。
vivi在進行分區時可以將分區格式化為若干bon分區(
read_partition_info() )
在bon的notifier_add()處理中,調用read_partition_info()讀取分區開頭8字節是否是bon分區的MAGIC標志
**讀bon.c代碼,發現bon是將整個mtd設備(也就是整個nand flash)作為操作對象,而并不是一個mtd分區一個bon分區。
似乎有點好理解了,(bon設備不依賴mtd分區),vivi 在用bon分區時,將分區信息寫入nand
flash 頭部的512字節區域內,bon視整個flash是一個mtd設備,
然后通過read_partition_info()將bon分區信息讀出,在內存中構建分區信息,所以在mtdblock.c代碼中沒有使用到mtd設備的分區信息,所以也就是不能用 root =/dev/mtdblock/0方式加載.
**在vivi的代碼中已經寫死了整個nand
flash的分區使用規則,如果不使用bonfs的話,就沒有必要進行bon分區,分區只是搽除數據和寫入標志,還有其他一些信息
bon設備支持 add_mtd_device(s3c2440_mtd) 方式添加的mtd設備
如何讀取和校驗bon分區的:
read_partition_info(){
mtd->read_oob((
mtd->size - mtd->erasesize),8) 從erasesize開始讀8字節oob數據
if(
oob[5] == 0xff){
buf
= mtd->read( ( mtd->size - mtd->erasesize),512) 讀512字節,bon分區信息存儲在這512字節區間內,比如分區偏移,大小,標志等等
if(
buf[0~7]!=BON_MAGIC){
非法bon分區
}
}
devfs_mk_dir("bon") 創建 /dev/bon
從mtd(nand flash)中讀取bon分區表信息,構建分區數組
devfs_register()
注冊子設備文件 /dev/bon/0,1,2,3 (子設備號根據vivi對nand flash的分區)
統計bad_block信息
}
bon_sizes[] 分區大小 ,以k為計算單位
bon_blksizes[]
分區塊大小
.bon分區作為啟動root分區
cmd
: char linux_cmd[] = "noinitrd root=/dev/bon/2 init=/linuxrc
console=ttyS0";
察看 main.c中 root_dev_names[]列表中并不存在bon的設備類型,原因在于bon是注冊進入devfs,是fs加載之前便已經初始化完畢了,
所以傳遞以上內核參數是可以找到對應的設備的.
root_dev_names[]定義的是固定的設備名稱和設備編號,此時就需要實現此設備的驅動程序必須與設備編號進行匹配對應了,也就是靠devfs_register(MAJOR)注冊的設備主編號
.bon分區啟動
start_kernel(){
1.parse_command()
分析命令行參數
root=/dev/bon/2
2.查找啟動設備的主設備編號
初始化所有設備驅動__initcall
在設備驅動中,bon注冊一個devfs的塊設備名稱 /dev/bon,然后注冊為mtd user ,register_mtd_user()
當mtd設備驅動被初始話時,啟動nand_chip驅動去搜索flash設備,通過一系列的寄存器的交互,發現了mtd設備,
如果當前沒有把分區開關打開的話則將整個flash當作一個mtd設備加入mtd_table[],同時通知mtd user,也就是告知
bon,當前已經有mtd設備掛入,bon得到控制權了,他就讀取mtd設備的開頭512字節的數據,構造分區表信息,和校驗分區是否合法,
bon將每個分區注冊進入devfs作為子設備(/dev/bon/0)
3.回到步驟2,由于bon沒有存在root_dev_names[]靜態表中,所以內核不知道其major編號,所以在跳過了nfs,floppy啟動判別之后,
內核拿著bon名字到devfs進行查詢此設備的主次設備編號,如果找到了則讀取超級塊,然后進行文件系統的識別
}
devfs
devfs 文件系統在內存中建立,而不是存儲在塊設備或者字符設備之上。
但是為了符合vfs文件系統的接口規范,以 devfs_entry來包裝 dentry,以devfs_inode來包裝 inode。
devfs文件系統中的devfs_entry存放在一個叫做 fs_info的結構變量中,以inode->ino節點編號作為索引可以查詢fs_info中的devfs_entry
數據結構
===============
1. devfs_entry : 描述devfs中的目錄項
typedef
struct devfs_entry * devfs_handle_t;
struct
fcb_type : 此結構類型概括了 字符設備/塊設備/常規文件的信息,包括了設備的特性,安全管理和最重要的驅動函數入口
devfs_entry存放在fs_info->table[]中
2.devfsd
此為一服務進程,觀察devfs 目錄項條目的運行狀態,比如加入新的entry, devfs_register()會觸發DEVFSD_NOTIFY_REGISTERED,devfsd便可以進行一些新增設備的處理工作 。
函數
==============
1.get_devfs_entry_from_vfs_inode()
根據inode->ino,在fs_info->table中搜尋devfs_entry對象
+++++++++++
1.訪問devfs塊設備文件
命令: cat
/dev/mtdblock/0
mtdblock/0
的主設備號31,次設備號 12 ,之前是由mtd驅動調用 devfs_register()注冊進入內核,并在devfs的fs_info->table[]中占有一席之地。
用戶在打開設備文件進入內核空間,在內核空間中,fs層通過path_walk()等函數找到/dev/mtdblock/0對應的dentry信息,
然后構建出file結構,將dentry->inode 和file作為參數,傳入devfs文件系統的
dev_open()函數。
devfs文件系統根據inode->ino編號在fs_info->table[]中找出mtd設備的devfs_entry對象,在devfs_entry->fcb->ops中存放著
設備驅動入口函數,驅動入口函數原型可以是file_operations或者block_device_operations. 然后將 devfs_entry->fcb->ops傳給inode->i_bdev->bd_op作為塊設備驅動入口。
在devfs_open()中,如果當前打開的是塊設備,就把缺省的塊設備訪問接口(def_blk_fops)付給file->f_op,然后調用file->f_open->open(inode,file)打開塊設備.
**所以對比字符設備和塊設備,塊設備的訪問比字符設備多了一層
block_device_operation. 字符設備可以將驅動寫在file_operations中,但塊設備必須寫在block_device_operation, 但要訪問設備的話都是通過 file_operation訪問,塊設備利用def_blk_fops進行了file_operation到block_device_operation的轉換。
2.塊設備的讀寫
ll_rw_block
devfs_register
===================
embedded_arch
1. bootloader : vivi/u-boot
2. kernel_image
3. root_fs
bootloader通過jtag燒寫到flash rom
bootloader通過rs232將kernel_image寫入flash,或者bootloader從rs232/ethernet動態加載kernel_image
kernel_image加載fs,可以采取nfs/mtd方式加載
kernel支持mtd/nand文件系統驅動
bootloader通過參數決定從何處掛載root-fs
fs
1. 新的文件系統注冊
register_filesystem()
find_filesystem()
kern_mount()
數據結構:
1.file_system_type
struct
file_system_type {
const
char *name;
int
fs_flags;
struct
super_block *(*read_super) (struct super_block *, void *, int);
struct
module *owner;/*動態加載的模塊,編譯進內核則為0*/
struct
vfsmount *kern_mnt; /* For kernel mount, if it's FS_SINGLE fs */
struct
file_system_type * next;
};
2.file_systems(super.c)定義了文件系統類型的鏈表
IO訪問
1.外設訪問的方式
*IO端口訪問 : 提供特殊的端口訪問指令,比如inb,outb
*端口映射: 將外設register映射到cpu內存空間,用普通操作指令便可訪問外設register 。非常方便,cpu開銷比較小,指令簡單
2.防止優化
對于register映射到內存的方式,將碰到兩種麻煩的可能。其一,硬件采用cache的方式來加快對內存地址的訪問;其二,編譯器會優化
操作指令,比如寫入內存數據可能會被放入寄存器。以上兩種情況都將導致訪問外射IO內存地址空間失敗。
所以為了避免這種情況,linux初始化時禁用訪問io地址空間時硬件cache功能;對于編譯器的解決方法就是采用barrier()
3.io端口地址的請求和釋放
release_region
request_region
4. io讀寫
inb(),insb(),outb(),outsb()
inb_p
暫停讀
io資源分配
1. request_region()
請求io映射端口內存地址范圍.內存區域用resource數據結構進行表示。
resource表現一段內存地址,以樹型展示不同的區域。也就是簡單的理解為一段內存地址可以又分隔為許多內存地址,所以resource具有了parent,sibling,child屬性
ipc
source/ipc目錄內的ipc機制都是system V 新增的通信機制,包括 共享內存,消息隊列和信號量
sys_pipe的實現在arch/i386/kernel中,似乎跟具體的平臺有某種關系,這是何故呢?
*pipe的實現比較簡單,但是具有所有文件具有的特征,區別在于pipe的inode節點中存在一個 pipe_operation的操作跳轉,兩個進程一頭寫一頭讀,數據靠內核中申請的一頁內存進行中轉。
夫進程在創建pipe之后,可以將pipe文件句柄傳遞到兩個子進程去,使其子進程之間可以進行單向的數據通信。
**fifo 命名管道
支持在無進程關系的不同進程之間通過打開命名管道名稱進行通信
==管道都是基于流,效率和使用方式都不是很好,比如不能提供有效的消息分割,導致要到應用層進行數據的解析,類似于tcp流的處理.
system-V 的ipc在 sys_ipc()函數內分流處理,類型有 semophera/message/share-memory
?sys_ipc()存在于 /arch/i386/kernel中,又不知何故難道不可移植的嗎?
kernel運行參數加載
include/asm-arm/mach/arch.h
arch/arm/mach-s3c2440/smdk.c
定義平臺信息結構
MACHINE_START(SMDK2440,
"Samsung-SMDK2440")
BOOT_MEM(0x30000000,
0x48000000, 0xe8000000)
BOOT_PARAMS(0x30000100) //定義linux啟示參數存放區域
FIXUP(fixup_smdk)
MAPIO(smdk_map_io)
INITIRQ(s3c2440_init_irq)
MACHINE_END
start_kernel(){
setup_arch(){
setup_machine()
//獲取平臺配置信息結構
struct machine_desc
{
檢索__arch_info_begin開始的位置的平臺信息是否匹配平臺類型
vmlinux-armv.lds.in
包含__arch_info_begin
定義*(.arch.info)
}
}
}
Linux啟動過程中硬件模塊的加載
設備驅動程序都用module_init(xx)進行登記
module_init()函數就是將 xxx函數地址負值到
initcall(void*) 的函數指針變量,并將其連接如.initcall.init節中. __attribute__
((unused,__section__ (".initcall.init")))
kernel啟動時,在函數do_initcalls()將 initcall.init節中的所有函數地址都執行一便,這樣就完成了靜態連接到kernel的驅動的初始化調用
mtd_nand
1.nand支持結構層次
fs
(user mode)
mtd
nand(driver)
** mtd/0
字符設備 driver/mtd/mtdchar.c
** mtdblock/0 塊設備 driver/mtd/mtdblock.c
分3層結構,nand驅動提供訪問flash 的底層接口函數,mtd抽象訪問接口,最上層就是文件系統層,目前可以使用的是jiffs,yaffs2等等
2. 基本數據結構
struct
mtd_info
定義一堆的訪問nand驅動的接口和數據
add_mtd_device() 將一個nand 設備注冊為一個mtd設備
add_mtd_partitions() 將分區注冊為mtd設備 (這種方式不能用于bon塊設備,因為這樣bon無法讀取正確的設備分區信息,bon是面向整個flash設備的)
struct
mtd_part 代表一個nand分區 , mtd_part::mtd指向 mtd_info
struct
mtd_notifier {
void (*add)(struct mtd_info *mtd);
注冊時回調fs的函數
void (*remove)(struct mtd_info *mtd);
struct mtd_notifier *next;
};
user-mode 的fs要提供mtd_notifier,當nand設備插拔時將進行通知(remove)到fs
register_mtd_user
(struct mtd_notifier *new);
unregister_mtd_user
(struct mtd_notifier *old);
以上這兩個方法供具體的fs調用
CONFIG_MTD_SMC_S3C2440_SMDK_PARTITION 決定了是否將一個mtd分區注冊為一個mtd設備
1. s3c2440 nand驅動
driver\mtd\nand\smc_sc2440.c
smc_insert()登記 nand partition信息
smc_scan() 掃描nand類型,并安裝nand處理函數到 mtd_info中去
add_mtd_partitions()
注冊mtd_info,支持partition
add_mtd_device(){
把每一個分區注冊為一個mtd設備
把每個mtd分區注冊進每一個mtd_notifier中去
}
2.
mtd_partition 定義mtd設備分區表
mtd_part 定義分區信息結構
每個mtd_part分區都視為mtd設備,所以都具有 mtd_info數據結構。并將mtd_part通過add_mtd_device()注冊進入mtd_table[]數組
mtd_part
擁有mtd_info的理由:
mtd_info定義了訪問mtd設備的接口,比如 mtd.read(). mtd_part作為mtd設備的一個區間,但也被看作是一個mtd設備,所以讀取mtd的起始位置與直接訪問mtd主設備不一致,多了一個偏移量。mtd_part.mtd.read()函數中計算出mtd主設備中自己的絕對位置,然后調用主mtd設備的read()接口函數,所以在訪問帶有分區的mtd設備時調用繞了一個小圈子。
mtd_info中的諸多函數在主設備中定義為 nand_xxx(),而對應著在mtd_part.mtd中也對應定義了 part_xxx(),但調用還是最終要進入 nand_xxx(). part_xxx()接口只是在為真正調用nand_xxx()而計算自己在主設備中的偏移量而已
。
所以對mtd來講,一個分區就是一個mtd設備,如果沒有劃分分區則此設備就是一個mtd設備
smc_insert
mtdblock
1. 初始化
init_mtdblock(){
devfs_register_blkdev() 注冊mtd塊設備到devfs文件系統
register_mtd_user() 注冊mtd設備插拔事件接收者
blk_init_queue() 初始化數據隊列
mtdblock_thread() 啟動工作線程
}
/drivers/mtd/mtdcore.c 代碼中通過add_mtd_device()將mtd/nand/smc_s3c2440.c中定義的分區mtd_part作為mtd_info設備注冊進系統mtd設備鏈表
修改分區信息:
smc_s3c2440.c::smc_partitions[],
開放 宏開關 CONFIG_MTD_SMC_S3C2440_SMDK_PARTITION
,系統將所有定義的分區作為mtd設備登記入mtd_table[]數組.(通過/dev/mtdblock/n便穿越mtdblock驅動到達mtd驅動層獲取mtd分區信息)
64M K9F1208U0M Flash
15bit寬
,32k pages = 16M
nand flash = 16M*4Plane = 64M
column Address 用于選擇 A,B,C區地址
出廠時如果是壞塊,在第一或者第二頁的C區的第6字節不為0xff
Flash由于在使用時會產生新的壞塊,所以必須采用replacement策略,即在寫入檢測為壞塊則跳躍到下一個塊進行處理。
在寫/讀/擦出操作時產生失敗,則有必要將此塊標示為壞塊
檢測到操作失敗
定位到頁所在塊的第一個頁位置
寫入C區517位置的值為非0xff
在讀寫之前判別好/壞塊的方法:
從塊編號換算到頁編號
NF_CMD(CMD_READ2);
// 0x50
NF_ADDR(VALIDADDR) //column 第6個字節,517位置, #define VALIDADDR 0x05
NF_ADDR() //PAGE raws
NF_RDDATA() 如果是0xff,表示為好塊,否則為壞塊
ECC校驗:
Samsung
2440的nandflash
controller在讀寫flash之后將自動產生ecc校驗碼存放在ECC寄存器,所以在執行寫頁操作之后,必須把ECC校驗碼寫入到512~527這個C區間內,一般都放置在512+8的位置;在讀操作時,將存儲的校驗碼讀出并與ECC寄存器的值進行比對即可
Flash 讀寫代碼:
BOOL
FMD_WriteSector(SECTOR_ADDR startSectorAddr, LPBYTE pSectorBuff, PSectorInfo
pSectorInfoBuff, DWORD dwNumSectors)
{
BYTE Status;
ULONG SectorAddr = (ULONG)startSectorAddr;
ULONG MECC;
if (!pSectorBuff &&
!pSectorInfoBuff)
{
RETAILMSG(1,(TEXT("FMD_WriteSector: Failed sector write 01.
\r\n")));
return(FALSE);
}
NF_RSTECC(); // Initialize ECC.
NF_nFCE_L(); // Select the
flash chip.
NF_CMD(CMD_RESET); // Send reset command.
NF_WAITRB(); // Wait for flash to complete
command.
while (dwNumSectors--)
{
ULONG blockPage = (((SectorAddr /
NAND_PAGE_CNT) * NAND_PAGE_CNT) | (SectorAddr % NAND_PAGE_CNT));
if (!pSectorBuff) //如果只是準備寫入page信息(512~517)
{
// If we are asked just to write
the SectorInfo, we will do that separately
NF_CMD(CMD_READ2); // Send read command. 讀第C區命令
NF_CMD(CMD_WRITE); // Send write command.
NF_ADDR(0); // Column = 0. 第一個字節開始(512)
NF_ADDR(blockPage & 0xff); // Page address. 頁行地址,共15位寬,所以共 2^15=32K pages = 16M
NF_ADDR((blockPage >> 8) & 0xff);
NF_ADDR((blockPage >> 16)
& 0xff);
WrPageInfo((PBYTE)pSectorInfoBuff); //寫入信息
pSectorInfoBuff++;
}
else
{
NF_RSTECC();
NF_MECC_UnLock();
NF_CMD(CMD_READ); // Send read command. 0x00
NF_CMD(CMD_WRITE); // Send write command.
NF_ADDR(0); // Column = 0.
NF_ADDR(blockPage & 0xff); // Page address.
NF_ADDR((blockPage >> 8) & 0xff);
NF_ADDR((blockPage >> 16)
& 0xff);
//
Special case to handle un-aligned buffer pointer.
if( ((DWORD) pSectorBuff) &
0x3)
{
WrPage512Unalign (pSectorBuff);
}
else
{
WrPage512(pSectorBuff); // Write page/sector data.
}
NF_MECC_Lock();
// Write the SectorInfo data to the
media.
//
if(pSectorInfoBuff)
{
WrPageInfo((PBYTE)pSectorInfoBuff); //寫入尾部信息 512~519
pSectorInfoBuff++;
}
else // Make sure we advance the Flash's write
pointer (even though we aren't writing the SectorInfo data)
{
BYTE TempInfo[] = {0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
WrPageInfo(TempInfo);
}
pSectorBuff += NAND_PAGE_SIZE;
MECC = NF_RDMECC0();
NF_WRDATA((MECC ) & 0xff);
NF_WRDATA((MECC >> 8) & 0xff);
NF_WRDATA((MECC >> 16) & 0xff);
NF_WRDATA((MECC >> 24) &
0xff);
}
//NF_CLEAR_RB();
NF_CMD(CMD_WRITE2); // Send write confirm
command.
//NF_DETECT_RB();
NF_WAITRB(); // Wait for command to
complete.
NF_CMD(CMD_STATUS);
Status = NF_RDDATA();
if (Status & 0x1)
{
SetKMode (bLastMode);
//
EdbgOutputDebugString("ERROR: FMD_WriteSector: failed sector
write.\r\n");
RETAILMSG(1,(TEXT("FMD_WriteSector: Failed sector write 01.
\r\n")));
return(FALSE);
}
++SectorAddr;
}
NF_nFCE_H(); // Deselect the
flash chip.
SetKMode (bLastMode);
return(TRUE);
}
BOOL FMD_ReadSector(SECTOR_ADDR startSectorAddr,
LPBYTE pSectorBuff, PSectorInfo pSectorInfoBuff, DWORD dwNumSectors)
{
ULONG
SectorAddr = (ULONG)startSectorAddr;
ULONG
MECC,SECC,Count;
BOOL
bLastMode = SetKMode(TRUE);
NF_RSTECC();
// Initialize ECC.
NF_nFCE_L();
// Select the flash chip.
NF_CMD(CMD_RESET); // Send
reset command.
NF_WAITRB(); // Wait
for flash to complete command.
while
(dwNumSectors--)
{
ULONG
blockPage = (((SectorAddr / NAND_PAGE_CNT) * NAND_PAGE_CNT) | (SectorAddr %
NAND_PAGE_CNT));
NF_WAITRB();
// Wait for flash to complete command.
NF_RSTECC();
if
(pSectorBuff)
{
NF_CMD(CMD_READ);
// Send read command.
NF_ADDR(0);
// Column = 0.
NF_ADDR(blockPage &
0xff); // Page address.
NF_ADDR((blockPage >> 8) & 0xff);
NF_ADDR((blockPage >> 16) & 0xff);
NF_WAITRB();
// Wait for command to complete.
// Handle unaligned buffer
pointer
NF_MECC_UnLock();
if( ((DWORD) pSectorBuff) & 0x3)
{
RdPage512Unalign (pSectorBuff);
}
else
{
RdPage512(pSectorBuff);
// Read page/sector data.
}
NF_MECC_Lock();
for (Count=0; Count<8; Count++)
NF_RDDATA();
這里是要跳開8個字節的 sectorInfo信息,之后的4字節就是ECC碼
MECC = NF_RDDATA() << 0;
MECC
|= NF_RDDATA() << 8;
MECC
|= NF_RDDATA() << 16;
MECC
|= NF_RDDATA() << 24;
NF_WRMECCD0(
((MECC&0xff00)<<8)|(MECC&0xff) );
NF_WRMECCD1(
((MECC&0xff000000)>>8)|((MECC&0xff0000)>>16) );
if
(NF_RDESTST0 & 0x3)
{
RETAILMSG(1,(TEXT("ecc error %x %x
\r\n"),NF_RDMECC0(),MECC));
NF_nFCE_H(); // Deselect the flash
chip.
SetKMode (bLastMode);
return FALSE;
}
}
if
(pSectorInfoBuff)
{
NF_CMD(CMD_READ2);
// Send read confirm command.
NF_ADDR(0); // Column = 0.
NF_ADDR(blockPage &
0xff); // Page address.
NF_ADDR((blockPage >> 8)
& 0xff);
NF_ADDR((blockPage >> 16) & 0xff);
NF_WAITRB();
// Wait for command to complete.
RdPageInfo((PBYTE)pSectorInfoBuff);
// Read page/sector information.
NF_RDDATA();
// Read/clear status.
NF_RDDATA();
//
pSectorInfoBuff++;
}
++SectorAddr;
pSectorBuff += NAND_PAGE_SIZE;
}
NF_nFCE_H();
// Deselect the flash chip.
SetKMode
(bLastMode);
return(TRUE);
}
tasklet
1.數據結構
struct
tasklet_struct/*bh tasklet*/
{
struct
tasklet_struct *next;
unsigned
long state;
atomic_t
count;
void
(*func)(unsigned long); /*服務程序*/
unsigned
long data;
};
2.常用的操作方法 (linux/interrupt.h
kernel/softirq.c)
DECLARE_TASKLET(name,
func, data)
DECLARE_TASKLET_DISABLED(name,
func, data)
tasklet_schedule()
tasklet_disable() tasklet::count加1
tasklet_enable()
tasklet_kill() 從調度隊列中刪除
mark_bh()
**tasklet 的引用計數
為0才能被調度執行
系統的軟中斷似乎都使用了tasklet方式,包括傳統的BH方式
**TIMER_BH/TQUEUE_BH在時鐘中斷產生時將這兩個處理隊列放入軟中斷調度任務隊列
中斷在某個cpu上產生,在中斷處理函數中調度tasklet_schedule(),把tasklet掛入此cpu的tasklet處理隊列中,
tasklet_action()被軟中斷調用,然后將當前cpu的tasklet隊列取出,循環執行一遍, (tasklet執行一次)
**在一個cpu產生的tasklet則必定在這個cpu上被執行
tasklet實現方式:
DECLARE_TASKLET(xxxx)
tasklet_schedule(xxx)
tqueue
tqueue也是Bh的實現方式,但在2.5版本已經廢棄
tqueue.h定義了基本的數據類型
queue_task()
task_queue
tq_timer, tq_immediate, tq_disk;
**tq_timer 在時鐘中斷產生時被mark_bh進入調度隊列的
**tq_immediate加入隊列時候必須手動調用mark_bh讓系統進行調度
timer.c 定義了處理隊列
DECLARE_TASK_QUEUE(tq_timer);
DECLARE_TASK_QUEUE(tq_immediate);
void tqueue_bh(void){
run_task_queue(&tq_timer); 執行隊列任務
}
void immediate_bh(void){
run_task_queue(&tq_immediate)
}
以上兩個標準的BH處理函數對應TQUEUE_BH和IMMEDIATE_BH,這兩個函數也被包裝成標準的tasklet供軟中斷調度執行
采用tqueue的BH方式的實現:
queue_task(&short_task,
&tq_immediate); 加入一個task
mark_bh(IMMEDIATE_BH); 調度運行
隊列BH方式,在被調度執行完畢后tq_struct都將被刪除,下次使用必須再次將自己加入(queue_task)
Uart
driver/serial/serial_core.c 包含了串口操作的核心代碼
當uart設備掛入devfs文件系統時,設備名稱命名為ttyS%d
struct uart_driver
struct uart_ops 定義操作接口函數列表
struct uart_port 定義設備端口信息(包括一系列硬件配置信息)
struct console 控制臺設備
static struct console s3c2440_cons = {
name: "ttyS",
write: s3c2440_console_write,
device: s3c2440_console_device,
wait_key: s3c2440_console_wait_key,
setup: s3c2440_console_setup,
flags: CON_PRINTBUFFER,
index: -1,
};
void __init s3c2440_console_init(void)
{
register_console(&s3c2440_cons);
}
創建jffs2文件系統
kernel編譯支持jffs2
將cramfs mount 到 /mnt
tar cf fs.tar mnt
cd /tmp
tar xf fs.tar
mkfs.jffs2 -r mnt -o root.jffs2 -l
配置網卡 ifconfig
eth0 192.18.14.5 up
wget http://192.168.14.3/imagewrite
wget http://192.168.14.3/root.jffs2
#./imagewrite /dev/mtd/0
root_qtopia_2440.cramfs:2m
1.dependence tools
wget
imagewrite
apache
mkfs.jffs2
2.configuration
啟動httpd ,默認/var/www/html為主目錄.
ln
-s /home/scott/kernel /var/www/html/kernel
wget http://192.168.103.55/kernel/zImage
磁盤緩沖
1. buffer cache
磁盤io操作依賴buffer cache ,一個buffer對應一個磁盤塊
buffer
cache 用于提高磁盤接收和存儲數據的性能.
*
data structure
a
set of buffer head
a
hash table
buffer_head
包含數據緩沖區,但內核也保留一些不含數據緩沖區的buffer_head,避免分配和釋放的開銷
2. page cache
一個page對應多個磁盤塊 (x86中的PAGE_SIZE=4kb,一般的磁盤塊大小為512k/1024k)
內核在IO操作之前必須檢查待獲取的數據是否存在于page cache中
*
page結構中表示緩沖數據用
address_space數據結構來描述(比如在文件中的偏移量)
*
struct address_space
表示的頁類型:
常規文件或者目錄信息
內存映射文件信息(mmap)
原始的塊設備數據u
用戶態進程被交換到交換空間的數據
進程間通信的共享內存區域數據
kernel
根據頁類型執行適當的操作函數來完成數據的讀取(regular file,block device,swap area ...)
*
address_space_operations 定義如何處理頁的操作表
pm.c 實現apm電源管理框架
mizi實現的代碼在 arm\kernel\apm2.c中
mizi實現了APM_LCD_ON/OFF,APM_MZ_SLEEP,APM_IOC_SUSPEND
2440體系中,不同的設備調用pm_register()接口注冊電源回調函數,在內核中的pm_list存放電源管理設備鏈表。
在用戶層通過訪問/dev/apm_bios通過ioctl發送APM_LCD_ON等等控制命令,這些命令將被傳遞到具體的設備驅動之電源回調函數之中
調度初始化
start_kernel{
sched_init{
init_bh(TIMER_BH,
timer_bh);
init_bh(TQUEUE_BH,
tqueue_bh);
init_bh(IMMEDIATE_BH,
immediate_bh);
}
}
進程和調度
1.kernel_thread()
用于創建內核線程,其內部匯編實現調用系統功能調用0x80,由sys_clone實現
觀看一下,有點好玩
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? 比較堆棧指針,如果變了就認為是子進程*/
"je
1f\n\t" /* parent - jump */
/*
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) /* EAX= __NR_clone*/
:"0"
(__NR_clone), "i" (__NR_exit),
"r" (arg), "r" (fn),
"b" (flags | CLONE_VM)
:
"memory");
return
retval;
}
塊設備驅動
1.request_queue
request_queue 包含buffer_head隊列頭,對列尾
buffer_head 緩沖數據頭信息,buffer_head維持內存數據區域
request_queue { head,tail}
buffer_head
buffers
in ram
塊大小是sector的整數倍但不能超過 PAGE_SIZE,而且必須是2的冪
2.最多255個設備,由全局變量blkdevs維持
全局變量
bon_blksizes (int**) 存放指定設備的次設備塊大小的數組
bon_blksizes[i]
= 1024;
blksize_size[BON_MAJOR]
= bon_blksizes;
.塊設備的讀寫
塊設備通過塊隊列進行緩沖讀寫,由專門的工作線程完成工作
編寫塊設備驅動必須提供一個塊隊列和一個塊處理接口函數(request_fn_proc)
blk_init_queue(){ 初始化塊隊列
}
內存管理
內存管理硬件限制:
DMA設備只能訪問 16M地址空間
線性地址空間太小,不能訪問所有的物理內存(比如32位不能過4GB)
linux將物理內存劃分為3部分:ZONE_DMA
,ZONE_NORMAL ,ZONE_HIGHMEM
1.Page
描述物理頁面的數據結構
2. zone
描述相同屬性的page的集合,分3種zone
ZONE_DMA
ZONE_NORMAL is all physical memory from 16MB to 896MB.
On other (more fortunate) architectures, ZONE_NORMAL is all available memory
ZONE_HIGHMEM all memory above the physical 896MB
mark,cannot be directly accessed by kernel, but not be used on 64-bit
architectures,not existed
struct
zone_struct 描述內存區結構
3. node
將相同屬性的區組織成節
4.內存分配
allocate_page() 分配頁
__get_free_pages() 分配頁,返回頁的虛擬地址
kmalloc() 分配指定字節大小的內存空間
5.內存分配標志
The
flags are broken up into three categories: action modifiers, zone modifiers,
and types. Action modifiers specify how the kernel is supposed to allocate the
requested memory. In certain situations, only certain methods can be employed
to allocate memory. For example, interrupt handlers must instruct the kernel
not to sleep (because interrupt handlers cannot reschedule) in the course of
allocating memory. Zone modifiers specify from where to allocate memory. As you
saw earlier in this chapter, the kernel divides physical memory into multiple
zones, each of which serves a different purpose. Zone modifiers specify from
which of these zones to allocate. Type flags specify a combination of action
and zone modifiers as needed by a certain type of memory allocation. Type flags
simplify specifying numerous modifiers; instead, you generally specify just one
type flag. The GFP_KERNEL is a type flag, which is used for code in process
context inside the kernel. Let's look at the flags.
NUMA
=====
某些系統內存是非勻質的,也就是訪問不同的內存地址時所花費的開銷是不同的
所以系統將內存劃分為多個結( memory is partitioned
in several nodes),在同一個結中cpu訪問時間是一致的
IBM
compatible PC is UMA,thus NUMA support is not really required
buddy system algorithm
========================
軟中斷
1. 在硬中斷中觸發軟中斷,并轉入BH部分,等待調度線程調用do_softirq()來執行軟中斷入口函數
2.4內核4個軟中斷向量
2.6則有6個
中斷向量依次0~n,有限級依次降低
最近一個中斷返回的時候看起來就是執行do_softirq()的最佳時機。因為TASKLET_SOFTIRQ和HI_SOFTIRQ已經被觸發了
2. 軟中斷負荷:(軟中斷內核處理線程)
內核實際選中的方案是不立即處理重新觸發的軟中斷。而作為改進,當大量軟中斷出現的時候,
內核會喚醒一組內核線程來處理這些負載。這些線程在最低的優先級上運行(nice值是19),
這能避免它們跟其它重要的任務搶奪資源。但它們最終肯定會被執行,所以,這個折中方案能夠保證在軟中斷
負擔很重的時候用戶程序不會因為得不到處理時間而處于饑餓狀態
只要do_softirq()函數發現已經正在執行過的內核線程重新觸發了它自己,軟中斷內核線程就會被喚醒。
#此內核線程方式2.4中似乎不存在
3. 工作隊列 workqueue_struct 2.4中不存在
在工作隊列和軟中斷/tasklet中作出選擇非常容易。
如果推后執行的任務需要睡眠,那么就選擇工作隊列。如果推后執行的任務不需要休睡眠,那么就選擇軟中斷或tasklet
工作隊列中的對象具有延遲執行的定時器
當前的2.6版內核中,有三種可能的選擇:軟中斷、tasklets和工作隊列
4. 軟中斷與驅動程序:
編寫驅動程序調用request_irq()注冊中斷入口
調用open_softirq()注冊bh的軟中斷處理入口
中斷產生,執行top half, 調用 tasklet_scehedule()加入工作函數,調用
cpu_raise_softirq()觸發軟中斷
在系統的schedule()中執行do_softirq(),檢測軟中斷向量,依次執行每一個tasklet入口函數
5. 軟中斷初始化
start_kernel(){
softirq_init(){
初始化32個tasklet,因為存在32個BH處理函數
注冊軟中斷的中斷函數TASKLET_SOFTIRQ(tasklet_action),HI_SOFTIRQ(tasklet_hi_action)
}
}
時鐘中斷
1. SA_INTERRUPT: 中斷期間不允許中斷再次產生
top_half:
中斷觸發 >>
xxx_BH >> MarkBH >> Task_Schedule
bottom_half:
Schedule >> do_softirq >>
check_task_vec(HI_SOFTIRQ/TASKLET_SOFTIRQ) >> exec_tasklet
sample:
時鐘中斷0,觸發時鐘計時(jiffies++),調用MarkBh將bh函數推入TaskLet隊列等待調度執行;在調度時,檢測軟中斷觸發標志,循環執行完中斷對應的tasklet隊列。
tasklet這種軟中斷處理方式跟irq_desc硬中斷處理有相似但也有不同的地方。兩種處理方式都允許一個中斷向量綁定若干個中斷處理函數
pc的時鐘設置為100MHZ , 10us
過高設置時鐘頻率導致調度overhead,可能引起用戶空間無法響應的情況
2.時鐘中斷初始化
start_kernel{
time_init{
setup_irq(0,irq0);
//irq0 -- irq_action 時鐘中斷
}
}
3.時鐘中斷的處理函數 timer_interrupt() (time.c) ,此種中斷類型SA_INTERRUPT,irq0
所以不能被其他共享(也許為了提高響應速度防止時鐘扭曲,在這里進行了request_irq(0,SA_SHIRQ)的嘗試,一直失敗,看了代碼才明白)
4.中斷處理過程
timer_interrupt(){
basic
do something
do_timer_interrupt(){
do_timer(){
jiffies++
mark_bh(TIMER_BH){
觸發后半部執行
tasklet_hi_schedule(bh_task_vec+nr)
將bh部分加入調度隊列
如果tq_timer隊列非空,則把定時器隊列掛入調度隊列,mark_bh(TQUEUE_BH)
}
}
}
}
#
void (*bh_base[32])(void); 標準的32個BH回調函數
#
struct tasklet_struct bh_task_vec[32]; 把bh_base包裝成32個tasklet
#
bh_task_vec在
softirq_init()時被初始化32個tasklet,統一設置為bh_action()處理函數
#
在時鐘中斷產生時將
bh_task_vec[TIMER_BH]推入 軟中斷調度隊列,調度器將循環執行tasklet整個鏈表.
所以在這里我為了將自己的函數掛入TIMER_BH隊列并隨時鐘中斷實時的被系統調度,我認為只要將自己的處理
函數包裝成一個tasklet,然后掛到bh_task_vec[TIMER_BH]之后就可以,這樣就能實現簡單的時鐘中斷響應
5.時鐘定時器
在TIMER_BH的實現函數timer_bh() [kernel/timer.c]中進行時鐘的更新和系統定時器的執行
timer_bh(){
update_times();
run_timer_list();
執行所有定時任務
}
定時任務添加: add_timer()
數據同步管理
1.原子操作
atomic_read
atomic_sub_and_test(int
i, atomic_t *v); 該函數從原子類型的變量v中減去i,并判斷結果是否為0,如果為0,返回真,否則返回假。
2.semaphore
DECLARE_MUTEX
DECLARE_MUTEX_LOCKED
init_MUTEX
sema_init
down
down_interruptible
down_trylock
up
3.waitqueue
init_waitqueue_head
wait_event_interruptible
等待事件喚醒,調用將被阻塞
wake_up
struct
wait_queue_head_t 等待隊列數據結構
在此數據結構中維持1把訪問鎖和一個進程鏈表,等待處理進程都將掛入此隊列
4.spinlock
掛起cpu,只能同時由一人操作,可用于中斷處理. 一般都利用lock 鎖住總線的方式來實現. spinlock只有在SMP情況下使用,單cpu時spinlock是空代碼
local_irq_disable
local_irq_enable
local_bh_disable
bh
local_bh_enable
local_irq_save
local_irq_restore
wait_event_interruptible(w,f) 將當前task加入等待隊列,內部循環檢測f,f如果為true,則馬上退出阻塞狀態;否則scadule()進行一次調度切換
wake_up_interruptible
== 中斷處理top half過程,如果是單處理器,此刻中斷是關閉的,所以不用考慮數據保護問題
但在多處理器的環境中,不同的中斷會同時在不同cpu上產生,所以共享的數據必須進行保護
== semaphore阻塞當前進程,將進行推入等待隊列,以待調度程序進行調度處理,所以不能在中斷處理函數中使用
spin_lock
freeze system
文件系統
1. cramfs
塊文件系統,解壓到內存讀,文件系統不可寫
命令: mkfs.cramfs
2. tmpfs
建立在vfs上,文件系統使用虛擬地址空間,所以掉電丟失數據,優點在于訪問,讀寫快速,可變大小
動態增加fs大小,可能將耗盡vm空間和物理內存或者swap空間。
命令: mount tmpfs /mnt/tmpfs -t tmpfs
創建一個新的最大 32 MB 的 tmpfs 文件系統
mount tmpfs
/dev/shm -t tmpfs -o size=32m
**添加到 /etc/fstab,應該是這樣:
tmpfs /dev/shm tmpfs size=32m 0 0
編譯: 啟用了Virtual memory file system support
3.ramfs
cramfs: Compressed ROM File System,是只讀文件系統,其容量上限只有 256MB.
romfs: 非常小的只讀文件系統.
jffs: 日志式快閃(Flash) 文件系統.
tmpfs: 可以用來將數據暫時保存在RAM中,而且容量可以隨著保存數據的量變 化.
ramfs: 也可以用來將數據暫時保存在RAM中,與tmpfs類似.
WOLF Linux在嵌入式設備環境下使用,支持flash,同時又要支持多平臺下的存儲卡,所以,在選擇文件系統時要考慮到實際應用.
WOLF Linux對文件系統的要求是:
要用盡可能少的內存
速度要快
臨時文件操作不能在flash上進行,因為flash的擦寫是有壽命的.
內核運行需要的文件和數據存放在flash上,而一些臨時文件則用內存作為存儲介質.所以內核運行采用兩種文件系統:jffs和 tmpfs.
Jffs 文件系統是在flash上操作,內部有專門對flash的優化.采用jffs的優點是內存占用少,速度快,體積小.現在很多嵌入式linux采用的是基于 EXT2的Ramdisk.Ramdisk在flash上要占用2MB以上,啟動時還要解壓縮到內存,需要占用內存10MB以上,缺點是浪費內存,啟動速度慢.內存需要10MB的Ramdisk中的內容采用jffs只需占flash 2.1MB左右.
tmpfs由于體積隨內容可增可減,所以它沒有浪費內存,一些臨時文件可以采用這種格式.
而書籍等數據文件是存放在存儲卡上的,這些存儲卡可能在不同的平臺上使用,比如,出版社用NTFS格式化了存儲卡,然后在上面加密拷貝了一些書籍.這時要求 WOLF Linux能識別該卡,并且完成閱讀軟件要求的讀寫操作,所以,支持存儲卡的文件系統更加多樣: FAT,VFAT
(Windows-95),NTFS,EXT等.
文件系統加載過程
start_kernel(){
init(){
do_basic_setup(){
硬件初始化
sock_init()
do_initcalls(); //調用所有靜態連接到內核的驅動初始化代碼,包括文件系統
filesystem_setup(){
init_devfs_fs(){
register_filesystem(
devfs)
kern_mount(devfs)
掛上devfs文件系統(devfs_read_super)
}
init_nfs_fs()
}
mount_root()
掛根分區
mount_devfs_fs()
從root上掛devfs
如果是initrd啟動,執行/linuxrc
}
}
execv(
/bin/init)
}
2. root文件系統加載
比如root=/dev/hda1
start_kernel(){
parse_options(){
checksetup(){
一次調用命令行參數對應的處理函數,比如,__setup("root=",
root_dev_setup);
root_dev_setup(){
從命令行中拾取 root=之后的參數
ROOT_DEV
= name_to_kdev_t(){
根據傳遞進入的root fs名稱到 root_dev_names數組中找尋對應的設備編號
將此編號加上子設備號 ,比如hda1 就加上1,hda2就加上2
用to_kdev_t()轉換成kdev_t作為root文件系統設備號返回
}
將設備名稱放入root_device_name數組中緩存,比如( root_device_name = hda1)
}
}
}
}
內核啟動時,分解根分區參數,然后根據名稱(main.c:root_dev_names[])找到對應的主設備編號(major),
掛載devfs文件系統到內存
初始化所有的驅動__initcall類型的函數,這些初始化函數會將驅動設備注冊進入devfs
執行mount_root(){
nfs() 啟動
floppy() 軟盤啟動
如果root=參數中的設備名稱未定義在root_dev_names[]列表之中,也就是說啟動設備編號(major)無法獲得,
則嘗試拿著設備名稱到devfs文件系統中去查找 devfs_find_handle(),如果找到存在這樣的條目,則獲取主,次設備編號,
然后調用這些設備驅動讀取super_block.
獲取super_block之后將其送入每一個文件系統,看看這super_block是屬于誰的
}
devfs_find_handle(devfs_handle_t
dir, const char *name,
unsigned int major, unsigned int minor,
char type, int traverse_symlinks)
如果name為NULL,則根據 major和minor進行搜索,否則就根據名稱查找
3. __setup()宏
安裝一些與內核啟動參數對應的處理函數,在parse_options()時遍歷到此參數時便調用對應的處理函數
這是一種內核啟動參數與內核驅動傳遞數據的一種方式
使用方式: __setup("com90io=",
com90io_setup);
此種方式只適合靜態編鏈的內核代碼,不適合模塊動態加載方式.
checksetup()執行與內核參數對應的處理函數
4. do_mount(){ 加載文件系統
}
文件系統都構件在塊設備上。設備文件 /dev/hda1 的inode信息中包含設備編號 kdev_t,根據設備編號可以找到此塊設備驅動
中斷處理
1.共享中斷
中斷信號n到來將依次調用中斷n的處理隊列中的處理函數,中斷處理函數必須能識別devid(devid唯一)
沒有一種可以檢測IRQ設備的方法
2.中斷檢測
必須在空閑的中斷線路上進行探測
3.中斷上半部和下半部共享使用的數據變量,在下半部處理時需要關閉中斷