socketref,再见!高德

https://github.com/adoggie

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  246 Posts :: 4 Stories :: 312 Comments :: 0 Trackbacks

常用链接

留言簿(54)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜


bh处理

1.三种旧式的bottom half 处理类型

IMMEDIATE_BH:  driver注册入tq_immediate队列,等待调度

TQUEUE_BH:       执行tq_timer,到系统tick产生时触发

TIMER_BH:    直接绑定到do_timer()处理函数

 

2.mark_bh()

       将激活32BH中的某一个,触发软中断来进入被调度状态

       此种软中断优先级为 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() )

bonnotifier_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 (子设备号根据vivinand 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_REGISTEREDdevfsd便可以进行一些新增设备的处理工作

      

 

函数

==============

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()注册进入内核,并在devfsfs_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_operationblock_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通过rs232kernel_image写入flash,或者bootloaderrs232/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的实现比较简单,但是具有所有文件具有的特征,区别在于pipeinode节点中存在一个 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 用于选择 ABC区地址

 

出厂时如果是坏块,在第一或者第二页的C区的第6字节不为0xff

 

Flash由于在使用时会产生新的坏块,所以必须采用replacement策略,即在写入检测为坏块则跳跃到下一个块进行处理。

 

//擦出操作时产生失败,则有必要将此块标示为坏块

       检测到操作失败

       定位到页所在块的第一个页位置

       写入C517位置的值为非0xff

 

在读写之前判别好/坏块的方法:

       从块编号换算到页编号

       NF_CMD(CMD_READ2);           // 0x50

       NF_ADDR(VALIDADDR)            //column 6个字节,517位置,      #define VALIDADDR     0x05

       NF_ADDR()          //PAGE raws

       NF_RDDATA()  如果是0xff,表示为好块,否则为坏块

      

ECC校验:

       Samsung 2440nandflash 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::count1

       tasklet_enable()     

       tasklet_kill()   从调度队列中删除

       mark_bh()

 

**tasklet 的引用计数 0才能被调度执行

 

系统的软中断似乎都使用了tasklet方式,包括传统的BH方式

       **TIMER_BH/TQUEUE_BH在时钟中断产生时将这两个处理队列放入软中断调度任务队列

 

 

中断在某个cpu上产生,在中断处理函数中调度tasklet_schedule(),tasklet挂入此cputasklet处理队列中,

tasklet_action()被软中断调用,然后将当前cputasklet队列取出,循环执行一遍, (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_BHIMMEDIATE_BH,这两个函数也被包装成标准的tasklet供软中断调度执行

 

采用tqueueBH方式的实现:

       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的集合,3zone

       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_SOFTIRQHI_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(){

                     初始化32tasklet,因为存在32BH处理函数

                     注册软中断的中断函数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++),调用MarkBhbh函数推入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_INTERRUPTirq0

       所以不能被其他共享(也许为了提高响应速度防止时钟扭曲,在这里进行了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);  标准的32BH回调函数

       # struct tasklet_struct bh_task_vec[32]; bh_base包装成32tasklet

       # bh_task_vec softirq_init()时被初始化32tasklet,统一设置为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情况下使用,cpuspinlock是空代码

       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采用的是基于 EXT2Ramdisk.Ramdiskflash上要占用2MB以上,启动时还要解压缩到内存,需要占用内存10MB以上,缺点是浪费内存,启动速度慢.内存需要10MBRamdisk中的内容采用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 就加上1hda2就加上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)

        如果nameNULL,则根据 majorminor进行搜索,否则就根据名称查找

      

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.中断上半部和下半部共享使用的数据变量,在下半部处理时需要关闭中断


Feedback

# re: 【Linux Kernel】 读码时写的一些笔记,毕竟写给自己看,有些乱(好好理解kernel,能更好写出高效的驱动和应用代码) 2008-07-04 09:56 呵呵
学习了  回复  更多评论
  

# re: 【Linux Kernel】 读码时写的一些笔记,毕竟写给自己看,有些乱(好好理解kernel,能更好写出高效的驱动和应用代码) 2008-07-10 23:24 梦在天涯
关于liunx kerne有什么好书,推荐一个,谢谢先啊!  回复  更多评论
  

# re: 【Linux Kernel】 读码时写的一些笔记,毕竟写给自己看,有些乱(好好理解kernel,能更好写出高效的驱动和应用代码) 2008-07-16 02:58 放屁啊狗
@梦在天涯
书太多,我可以提供ftp供你下载  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理