经验

目 录

  1. 经验
    1. 新手入门
    2. 一个简单程序的分析----深至内核
      1. open
      2. read
    3. lisolog文章检索
      1. index
      2. list1
      3. list2
      4. list3
      5. list4
      6. list5
    4. AKA推荐书籍
    5. linux论坛推荐资源
    6. 数据结构
    7. 重新编译
    8. 重建内核选项
    9. 调试技术
    10. ptrace进程跟踪
    11. 宏#与##
    12. lxr和glimpse
    13. 内核阅读介绍
    14. 内核重编译常见故障


经验

[目录]


新手入门

入门

    针对好多Linux 爱好者对内核很有兴趣却无从下口,本文旨在介绍一种解读linux内核源码的入门方法,而不是解说linux复杂的内核机制;

一.核心源程序的文件组织:

    1.Linux核心源程序通常都安装在/usr/src/linux下,而且它有一个非常简单的编号约定:任何偶数的核心(例如2.0.30)都是一个稳定地发行的核心,而任何奇数的核心(例如2.1.42)都是一个开发中的核心。

    本文基于稳定的2.2.5源代码,第二部分的实现平台为 Redhat Linux 6.0。

    2.核心源程序的文件按树形结构进行组织,在源程序树的最上层你会看到这样一些目录:

    ●Arch :arch子目录包括了所有和体系结构相关的核心代码。它的每一个子目录都代表一种支持的体系结构,例如i386就是关于intel cpu及与之相兼容体系结构的子目录。PC机一般都基于此目录;

    ●Include: include子目录包括编译核心所需要的大部分头文件。与平台无关的头文件在 include/linux子目录下,与 intel cpu相关的头文件在include/asm-i386子目录下,而include/scsi目录则是有关scsi设备的头文件目录;

    ●Init: 这个目录包含核心的初始化代码(注:不是系统的引导代码),包含两个文件main.c和Version.c,这是研究核心如何工作的一个非常好的起点。

    ●Mm :这个目录包括所有独立于 cpu 体系结构的内存管理代码,如页式存储管理内存的分配和释放等;而和体系结构相关的内存管理代码则位于arch/*/mm/,例如arch/i386/mm/Fault.c

    ●Kernel:主要的核心代码,此目录下的文件实现了大多数linux系统的内核函数,其中最重要的文件当属sched.c;同样,和体系结构相关的代码在arch/*/kernel中;

    ●Drivers: 放置系统所有的设备驱动程序;每种驱动程序又各占用一个子目录:如,/block 下为块设备驱动程序,比如ide(ide.c)。如果你希望查看所有可能包含文件系统的设备是如何初始化的,你可以看drivers/block/genhd.c中的device_setup()。它不仅初始化硬盘,也初始化网络,因为安装nfs文件系统的时候需要网络其他: 如, Lib放置核心的库代码; Net,核心与网络相关的代码; Ipc,这个目录包含核心的进程间通讯的代码; Fs ,所有的文件系统代码和各种类型的文件操作代码,它的每一个子目录支持一个文件系统,例如fat和ext2;

    ●Scripts, 此目录包含用于配置核心的脚本文件等。

    一般,在每个目录下,都有一个 .depend 文件和一个 Makefile 文件,这两个文件都是编译时使用的辅助文件,仔细阅读这两个文件对弄清各个文件这间的联系和依托关系很有帮助;而且,在有的目录下还有Readme 文件,它是对该目录下的文件的一些说明,同样有利于我们对内核源码的理解;

二.解读实战:为你的内核增加一个系统调用

    虽然,Linux 的内核源码用树形结构组织得非常合理、科学,把功能相关联的文件都放在同一个子目录下,这样使得程序更具可读性。然而,Linux 的内核源码实在是太大而且非常复杂,即便采用了很合理的文件组织方法,在不同目录下的文件之间还是有很多的关联,分析核心的一部分代码通常会要查看其它的几个相关的文件,而且可能这些文件还不在同一个子目录下。

    体系的庞大复杂和文件之间关联的错综复杂,可能就是很多人对其望而生畏的主要原因。当然,这种令人生畏的劳动所带来的回报也是非常令人着迷的:你不仅可以从中学到很多的计算机的底层的知识(如下面将讲到的系统的引导),体会到整个操作系统体系结构的精妙和在解决某个具体细节问题时,算法的巧妙;而且更重要的是:在源码的分析过程中,你就会被一点一点地、潜移默化地专业化;甚至,只要分析十分之一的代码后,你就会深刻地体会到,什么样的代码才是一个专业的程序员写的,什么样的代码是一个业余爱好者写的。

    为了使读者能更好的体会到这一特点,下面举了一个具体的内核分析实例,希望能通过这个实例,使读者对 Linux的内核的组织有些具体的认识,从中读者也可以学到一些对内核的分析方法。

以下即为分析实例:

【一】操作平台:

硬件:cpu intel Pentium II ;

软件:Redhat Linux 6.0; 内核版本2.2.5【二】相关内核源代码分析:

    1.系统的引导和初始化:Linux 系统的引导有好几种方式:常见的有 Lilo, Loadin引导和Linux的自举引导

    (bootsect-loader),而后者所对应源程序为arch/i386/boot/bootsect.S,它为实模式的汇编程序,限于篇幅在此不做分析;无论是哪种引导方式,最后都要跳转到 arch/i386/Kernel/setup.S, setup.S主要是进行时模式下的初始化,为系统进入保护模式做准备;此后,系统执行 arch/i386/kernel/head.S (对经压缩后存放的内核要先执行 arch/i386/boot/compressed/head.S); head.S 中定义的一段汇编程序setup_idt ,它负责建立一张256项的 idt 表(Interrupt Descriptor Table),此表保存着所有自陷和中断的入口地址;其中包括系统调用总控程序 system_call 的入口地址;当然,除此之外,head.S还要做一些其他的初始化工作;

    2.系统初始化后运行的第一个内核程序asmlinkage void __init start_kernel(void) 定义在/usr/src/linux/init/main.c中,它通过调用usr/src/linux/arch/i386/kernel/traps.c 中的一个函数

    void __init trap_init(void) 把各自陷和中断服务程序的入口地址设置到 idt 表中,其中系统调用总控程序system_cal就是中断服务程序之一;void __init trap_init(void) 函数则通过调用一个宏

    set_system_gate(SYSCALL_VECTOR,&system_call); 把系统调用总控程序的入口挂在中断0x80上;

    其中SYSCALL_VECTOR是定义在 /usr/src/linux/arch/i386/kernel/irq.h中的一个常量0x80; 而 system_call 即为中断总控程序的入口地址;中断总控程序用汇编语言定义在/usr/src/linux/arch/i386/kernel/entry.S中;

    3.中断总控程序主要负责保存处理机执行系统调用前的状态,检验当前调用是否合法, 并根据系统调用向量,使处理机跳转到保存在 sys_call_table 表中的相应系统服务例程的入口; 从系统服务例程返回后恢复处理机状态退回用户程序;

    而系统调用向量则定义在/usr/src/linux/include/asm-386/unistd.h 中;sys_call_table 表定义在/usr/src/linux/arch/i386/kernel/entry.S 中; 同时在 /usr/src/linux/include/asm-386/unistd.h 中也定义了系统调用的用户编程接口;

    4.由此可见 , linux 的系统调用也象 dos 系统的 int 21h 中断服务, 它把0x80 中断作为总的入口, 然后转到保存在 sys_call_table 表中的各种中断服务例程的入口地址 , 形成各种不同的中断服务;

    由以上源代码分析可知, 要增加一个系统调用就必须在 sys_call_table 表中增加一项 , 并在其中保存好自己的系统服务例程的入口地址,然后重新编译内核,当然,系统服务例程是必不可少的。

    由此可知在此版linux内核源程序中,与系统调用相关的源程序文件就包括以下这些:

1.arch/i386/boot/bootsect.S
2.arch/i386/Kernel/setup.S
3.arch/i386/boot/compressed/head.S
4.arch/i386/kernel/head.S
5.init/main.c
6.arch/i386/kernel/traps.c
7.arch/i386/kernel/entry.S
8.arch/i386/kernel/irq.h
9.include/asm-386/unistd.h

    当然,这只是涉及到的几个主要文件。而事实上,增加系统调用真正要修改文件只有include/asm-386/unistd.h和arch/i386/kernel/entry.S两个;

【三】 对内核源码的修改:

    1.在kernel/sys.c中增加系统服务例程如下:

asmlinkage int sys_addtotal(int numdata)
{
int i=0,enddata=0;
while(i<=numdata)
enddata+=i++;
return enddata;
}

    该函数有一个 int 型入口参数 numdata , 并返回从 0 到 numdata 的累加值; 当然也可以把系统服务例程放在一个自己定义的文件或其他文件中,只是要在相应文件中作必要的说明;

    2.把 asmlinkage int sys_addtotal( int) 的入口地址加到sys_call_table表中:

    arch/i386/kernel/entry.S 中的最后几行源代码修改前为:

... ...

.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork) /* 190 */
.rept NR_syscalls-190
.long SYMBOL_NAME(sys_ni_syscall)
.endr

    修改后为:

... ...
.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork) /* 190 */
/* add by I */
.long SYMBOL_NAME(sys_addtotal)
.rept NR_syscalls-191
.long SYMBOL_NAME(sys_ni_syscall)
.endr

    3. 把增加的 sys_call_table 表项所对应的向量,在include/asm-386/unistd.h 中进行必要申明,以供用户进程和其他系统进程查询或调用:

    增加后的部分 /usr/src/linux/include/asm-386/unistd.h 文件如下:

... ...

#define __NR_sendfile 187
#define __NR_getpmsg 188
#define __NR_putpmsg 189
#define __NR_vfork 190

/* add by I */

#define __NR_addtotal 191

4.测试程序(test.c)如下:

#include
#include
_syscall1(int,addtotal,int, num)

main()
{
int i,j;
  do
printf("Please input a number\n");
while(scanf("%d",&i)==EOF);
if((j=addtotal(i))==-1)
printf("Error occurred in syscall-addtotal();\n");
printf("Total from 0 to %d is %d \n",i,j);
}

    对修改后的新的内核进行编译,并引导它作为新的操作系统,运行几个程序后可以发现一切正常;在新的系统下对测试程序进行编译(*注:由于原内核并未提供此系统调用,所以只有在编译后的新内核下,此测试程序才能可能被编译通过),运行情况如下:

$gcc -o test test.c
$./test
Please input a number

36
Total from 0 to 36 is 666

    可见,修改成功;

    而且,对相关源码的进一步分析可知,在此版本的内核中,从/usr/src/linux/arch/i386/kernel/entry.S

    文件中对 sys_call_table 表的设置可以看出,有好几个系统调用的服务例程都是定义在/usr/src/linux/kernel/sys.c 中的同一个函数:

asmlinkage int sys_ni_syscall(void)
{
return -ENOSYS;
}

    例如第188项和第189项就是如此:

... ...

.long SYMBOL_NAME(sys_sendfile)
.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */
.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */
.long SYMBOL_NAME(sys_vfork) /* 190 */

... ...

    而这两项在文件 /usr/src/linux/include/asm-386/unistd.h 中却申明如下:

... ...
#define __NR_sendfile 187
#define __NR_getpmsg 188 /* some people actually want streams */
#define __NR_putpmsg 189 /* some people actually want streams */
#define __NR_vfork 190

    由此可见,在此版本的内核源代码中,由于asmlinkage int sys_ni_syscall(void) 函数并不进行任何操作,所以包括 getpmsg, putpmsg 在内的好几个系统调用都是不进行任何操作的,即有待扩充的空调用; 但它们却仍然占用着sys_call_table表项,估计这是设计者们为了方便扩充系统调用而安排的; 所以只需增加相应服务例程(如增加服务例程getmsg或putpmsg),就可以达到增加系统调用的作用。

[目录]


一个简单程序的分析----深至内核

                A small trail through the Linux kernel

Andries Brouwer, aeb@cwi.nl 2001-01-01

A program
---------------------------------------------------------------------------------------------------
#include <unistd.h>
#include <fcntl.h>
int main(){
        int fd;
        char buf[512];

        fd = open("/dev/hda", O_RDONLY);
        if (fd >= 0)
                read(fd, buf, sizeof(buf));
        return 0;
}
---------------------------------------------------------------------------------------------------

This little program opens the block special device referring to the first IDE disk, and if the open succeeded reads the first sector. What happens in the kernel? Let us read 2.4.0 source.

[目录]


open

The open system call is found in fs/open.c:

---------------------------------------------------------------------------------------------------
int sys_open(const char *filename, int flags, int mode) {
        char *tmp = getname(filename);
        int fd = get_unused_fd();
        struct file *f = filp_open(tmp, flags, mode);
        fd_install(fd, f);
        putname(tmp);
        return fd;
}
---------------------------------------------------------------------------------------------------

The routine getname() is found in fs/namei.c. It copies the file name from user space to kernel space:

---------------------------------------------------------------------------------------------------
#define __getname()     kmem_cache_alloc(names_cachep, SLAB_KERNEL)
#define putname(name)   kmem_cache_free(names_cachep, (void *)(name))

char *getname(const char *filename) {
        char *tmp = __getname();        /* allocate some memory */
        strncpy_from_user(tmp, filename, PATH_MAX + 1);
        return tmp;
}
---------------------------------------------------------------------------------------------------

The routine get_unused_fd() is found in fs/open.c again. It returns the first unused filedescriptor:

---------------------------------------------------------------------------------------------------
int get_unused_fd(void) {
        struct files_struct *files = current->files;
        int fd = find_next_zero_bit(files->open_fds,
                                    files->max_fdset, files->next_fd);
        FD_SET(fd, files->open_fds);    /* in use now */
        files->next_fd = fd + 1;
        return fd;
}
---------------------------------------------------------------------------------------------------

Here current is the pointer to the user task struct for the currently executing task.

The routine fd_install() is found in include/linux/file.h. It just stores the information returned by filp_open()

---------------------------------------------------------------------------------------------------
void fd_install(unsigned int fd, struct file *file) {
        struct files_struct *files = current->files;
        files->fd[fd] = file;
}
---------------------------------------------------------------------------------------------------

So all the interesting work of sys_open() is done in filp_open(). This routine is found in fs/open.c:

---------------------------------------------------------------------------------------------------
struct file *filp_open(const char *filename, int flags, int mode) {
        struct nameidata nd;
        open_namei(filename, flags, mode, &nd);
        return dentry_open(nd.dentry, nd.mnt, flags);
}
---------------------------------------------------------------------------------------------------

The struct nameidata is defined in include/linux/fs.h. It is used during lookups.

---------------------------------------------------------------------------------------------------
struct nameidata {
        struct dentry *dentry;
        struct vfsmount *mnt;
        struct qstr last;
};
---------------------------------------------------------------------------------------------------

The routine open_namei() is found in fs/namei.c:

---------------------------------------------------------------------------------------------------
open_namei(const char *pathname, int flag, int mode, struct nameidata *nd) {
        if (!(flag & O_CREAT)) {
                /* The simplest case - just a plain lookup. */
                if (*pathname == '/') {
                        nd->mnt = mntget(current->fs->rootmnt);
                        nd->dentry = dget(current->fs->root);
                } else {
                        nd->mnt = mntget(current->fs->pwdmnt);
                        nd->dentry = dget(current->fs->pwd);
                }
                path_walk(pathname, nd);
                /* Check permissions etc. */
                ...
                return 0;
        }
        ...
}
---------------------------------------------------------------------------------------------------

An inode (index node) describes a file. A file can have several names (or no name at all), but it has a unique inode. A dentry (directory entry)describes a name of a file: the inode plus the pathname used to find it. Avfsmount describes the filesystem we are in.

So, essentially, the lookup part op open_namei() is found in path_walk():

---------------------------------------------------------------------------------------------------
path_walk(const char *name, struct nameidata *nd) {
        struct dentry *dentry;
        for(;;) {
                struct qstr this;
                this.name = next_part_of(name);
                this.len = length_of(this.name);
                this.hash = hash_fn(this.name);
                /* if . or .. then special, otherwise: */
                dentry = cached_lookup(nd->dentry, &this);
                if (!dentry)
                        dentry = real_lookup(nd->dentry, &this);
                nd->dentry = dentry;
                if (this_was_the_final_part)
                        return;
        }
}
---------------------------------------------------------------------------------------------------

Here the cached_lookup() tries to find the given dentry in a cache of recently used dentries. If it is not found, the real_lookup() goes to the filesystem, which probably goes to disk, and actually finds the thing.After path_walk() is done, the nd argument contains the required dentry,which in turn has the inode information on the file. Finally we do dentry_open() that initializes a file struct:

---------------------------------------------------------------------------------------------------
struct file *
dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags) {
        struct file *f = get_empty_filp();
        f->f_dentry = dentry;
        f->f_vfsmnt = mnt;
        f->f_pos = 0;
        f->f_op = dentry->d_inode->i_fop;
        ...
        return f;
}
---------------------------------------------------------------------------------------------------

So far the open. In short: walk the tree, for each component hope the information is in cache, and if not ask the file system. How does this work? Each file system type provides structs super_operations,file_operations, inode_operations, address_space_operations that contain the addresses of the routines that can do stuff. And thus

---------------------------------------------------------------------------------------------------
struct dentry *real_lookup(struct dentry *parent, struct qstr *name, int flags) {
        struct dentry *dentry = d_alloc(parent, name);
        parent->d_inode->i_op->lookup(dir, dentry);
        return dentry;
}
---------------------------------------------------------------------------------------------------

calls on the lookup routine for the specific fiilesystem, as found in the struct inode_operations in the inode of the dentry for the directory in which we do the lookup.

And this file system specific routine must read the disk data and search the directory for the file we are looking for. Good examples of file systems are minix and romfs because they are simple and small. For example,in fs/romfs/inode.c:

---------------------------------------------------------------------------------------------------
romfs_lookup(struct inode *dir, struct dentry *dentry) {
        const char *name = dentry->d_name.name;
        int len = dentry->d_name.len;
        char fsname[ROMFS_MAXFN];
        struct romfs_inode ri;
        unsigned long offset = dir->i_ino & ROMFH_MASK;
        for (;;) {
                romfs_copyfrom(dir, &ri, offset, ROMFH_SIZE);
                romfs_copyfrom(dir, fsname, offset+ROMFH_SIZE, len+1);
                if (strncmp (name, fsname, len) == 0)
                        break;
                /* next entry */
                offset = ntohl(ri.next) & ROMFH_MASK;
        }
        inode = iget(dir->i_sb, offset);
        d_add (dentry, inode);
        return 0;
}

romfs_copyfrom(struct inode *i, void *dest,
               unsigned long offset, unsigned long count) {
        struct buffer_head *bh;

        bh = bread(i->i_dev, offset>>ROMBSBITS, ROMBSIZE);
        memcpy(dest, ((char *)bh->b_data) + (offset & ROMBMASK), count);
        brelse(bh);
}
(All complications, all locking, and all error handling deleted.)
---------------------------------------------------------------------------------------------------

[目录]


read

Given a file descriptor (that keeps the inode and the file position of the file) we want to read. In fs/read_write.c we find:

---------------------------------------------------------------------------------------------------
ssize_t sys_read(unsigned int fd, char *buf, size_t count) {
        struct file *file = fget(fd);
        return file->f_op->read(file, buf, count, &file->f_pos);
}
---------------------------------------------------------------------------------------------------

That is, the read system call asks the file system to do the reading,starting at the current file position. The f_op field was filled in the dentry_open() routine above with the i_fop field of an inode.

For romfs the struct file_operations is assigned in romfs_read_inode(). For a regular file (case 2) it assigns generic_ro_fops. For a block special file (case 4) it calls init_special_inode() (see devices.c) which assigns
def_blk_fops.

How come romfs_read_inode() was ever called? When the filesystem was mounted, the routine romfs_read_super() was called, and it assigned romfs_ops to the s_op field of the superblock struct.

---------------------------------------------------------------------------------------------------
struct super_operations romfs_ops = {
        read_inode:     romfs_read_inode,
        statfs:         romfs_statfs,
};
---------------------------------------------------------------------------------------------------

And the iget() that was skipped over in the discussion above (in romfs_lookup()) finds the inode with given number ino in a cache, and if it cannot be found there creates a new inode struct by calling get_new_inode()(see fs/inode.c):

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

struct inode * iget(struct super_block *sb, unsigned long ino) {
        struct list_head * head = inode_hashtable + hash(sb,ino);
        struct inode *inode = find_inode(sb, ino, head);
        if (inode) {
                wait_on_inode(inode);
                return inode;
        }
        return get_new_inode(sb, ino, head);
}

struct inode *
get_new_inode(struct super_block *sb, unsigned long ino,
              struct list_head *head) {
        struct inode *inode = alloc_inode();
        inode->i_sb = sb;
        inode->i_dev = sb->s_dev;
        inode->i_ino = ino;
        ...
        sb->s_op->read_inode(inode);
}
---------------------------------------------------------------------------------------------------

So that is how the inode was filled, and we find that in our case (/dev/hda is a block special file) the routine that is called by sys_read is def_blk_fops.read, and inspection of block_dev.c shows that that is the routine block_read():
---------------------------------------------------------------------------------------------------

ssize_t block_read(struct file *filp, char *buf, size_t count, loff_t *ppos) {
        struct inode *inode = filp->f_dentry->d_inode;
        kdev_t dev = inode->i_rdev;
        ssize_t blocksize = blksize_size[MAJOR(dev)][MINOR(dev)];
        loff_t offset = *ppos;
        ssize_t read = 0;
        size_t left, block, blocks;
        struct buffer_head *bhreq[NBUF];
        struct buffer_head *buflist[NBUF];
        struct buffer_head **bh;

        left = count;                   /* bytes to read */
        block = offset / blocksize;     /* first block */
        offset &= (blocksize-1);    /* starting offset in block */
        blocks = (left + offset + blocksize - 1) / blocksize;

        bh = buflist;
        do {
                while (blocks) {
                        --blocks;
                        *bh = getblk(dev, block++, blocksize);
                        if (*bh && !buffer_uptodate(*bh))
                                bhreq[bhrequest++] = *bh;
                }
                if (bhrequest)
                        ll_rw_block(READ, bhrequest, bhreq);
                /* wait for I/O to complete,
                   copy result to user space,
                   increment read and *ppos, decrement left */
        } while (left > 0);
        return read;
}
---------------------------------------------------------------------------------------------------

So the building blocks here are getblk(), ll_rw_block(), and wait_on_buffer().

The first of these lives in fs/buffer.c. It finds the buffer that already contains the required data if we are lucky, and otherwise a buffer that is going to be used.

---------------------------------------------------------------------------------------------------
struct buffer_head * getblk(kdev_t dev, int block, int size) {
        struct buffer_head *bh;
        int isize;

try_again:
        bh = __get_hash_table(dev, block, size);
        if (bh)
                return bh;
        isize = BUFSIZE_INDEX(size);
        bh = free_list[isize].list;
        if (bh) {
                __remove_from_free_list(bh);
                init_buffer(bh);
                bh->b_dev = dev;
                bh->b_blocknr = block;
                ...
                return bh;
        }
        refill_freelist(size);
        goto try_again;
}
---------------------------------------------------------------------------------------------------

The real I/O is started by ll_rw_block(). It lives in drivers/block/ll_rw_blk.c.

---------------------------------------------------------------------------------------------------
ll_rw_block(int rw, int nr, struct buffer_head * bhs[]) {
        int i;

        for (i = 0; i < nr; i++) {
                struct buffer_head *bh = bhs[i];

                bh->b_end_io = end_buffer_io_sync;

                submit_bh(rw, bh);
        }
}
---------------------------------------------------------------------------------------------------

Here bh->b_end_io specifies what to do when I/O is finished. In this case:

---------------------------------------------------------------------------------------------------
end_buffer_io_sync(struct buffer_head *bh, int uptodate) {
        mark_buffer_uptodate(bh, uptodate);
        unlock_buffer(bh);
}
---------------------------------------------------------------------------------------------------

So, ll_rw_block() just feeds the requests it gets one by one to submit_bh():

---------------------------------------------------------------------------------------------------
submit_bh(int rw, struct buffer_head *bh) {
        bh->b_rdev = bh->b_dev;
        bh->b_rsector = bh->b_blocknr * (bh->b_size >> 9);

        generic_make_request(rw, bh);
}
---------------------------------------------------------------------------------------------------

So, submit_bh() just passes things along to generic_make_request(), the routine to send I/O requests to block devices:

---------------------------------------------------------------------------------------------------
generic_make_request (int rw, struct buffer_head *bh) {
        request_queue_t *q;

        q = blk_get_queue(bh->b_rdev);
        q->make_request_fn(q, rw, bh);
}
---------------------------------------------------------------------------------------------------

Thus, it finds the right queue and calls the request function for that queue.

---------------------------------------------------------------------------------------------------
struct blk_dev_struct {
        request_queue_t         request_queue;
        queue_proc              *queue;
        void                    *data;
} blk_dev[MAX_BLKDEV];

request_queue_t *blk_get_queue(kdev_t dev)
{
        return blk_dev[MAJOR(dev)].queue(dev);
}
---------------------------------------------------------------------------------------------------

In our case (/dev/hda), the blk_dev struct was filled by hwif_init (from drivers/ide/ide-probe.c):
and this ide_get_queue() is found in drivers/ide/ide.c:

---------------------------------------------------------------------------------------------------
blk_dev[hwif->major].data = hwif;
        blk_dev[hwif->major].queue = ide_get_queue;

#define DEVICE_NR(dev)       (MINOR(dev) >> PARTN_BITS)

request_queue_t *ide_get_queue (kdev_t dev) {
        ide_hwif_t *hwif = (ide_hwif_t *) blk_dev[MAJOR(dev)].data;
        return &hwif->drives[DEVICE_NR(dev) & 1].queue;
}
---------------------------------------------------------------------------------------------------

This .queue field was filled by ide_init_queue():
And blk_init_queue() (from ll_rw_blk.c again):

---------------------------------------------------------------------------------------------------
ide_init_queue(ide_drive_t *drive) {
        request_queue_t *q = &drive->queue;

        q->queuedata = HWGROUP(drive);
        blk_init_queue(q, do_ide_request);
}

blk_init_queue(request_queue_t *q, request_fn_proc *rfn) {
        ...
        q->request_fn           = rfn;
        q->make_request_fn      = __make_request;
        q->merge_requests_fn    = ll_merge_requests_fn;
        ...
}
---------------------------------------------------------------------------------------------------

Aha, so we found the q->make_request_fn. Here it is:

---------------------------------------------------------------------------------------------------
       __make_request(request_queue_t *q, int rw, struct buffer_head *bh) {
               /* try to merge request with adjacent ones */
               ...
               /* get a struct request and fill it with device, start,length, ... */
               ...
               add_request(q, req, insert_here);
               if (!q->plugged)
                       q->request_fn(q);
       }

       add_request(request_queue_t *q, struct request *req,
                   struct list_head *insert_here) {
               list_add(&req->queue, insert_here);
       }

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

When the request has been queued, q->request_fn is called. What is that? We can see it above - it is do_ide_request() and lives in ide.c.

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

       do_ide_request(request_queue_t *q) {
               ide_do_request(q->queuedata, 0);
       }

       ide_do_request(ide_hwgroup_t *hwgroup, int masked_irq) {
               ide_startstop_t startstop;

               while (!hwgroup->busy) {
                       hwgroup->busy = 1;
                       drive = choose_drive(hwgroup);
                       startstop = start_request(drive);
                       if (startstop == ide_stopped)
                               hwgroup->busy = 0;
               }
       }

       ide_startstop_t
       start_request (ide_drive_t *drive) {
               unsigned long block, blockend;
               struct request *rq;

               rq = blkdev_entry_next_request(&drive->queue.queue_head);
               block = rq->sector;
               block += drive->part[minor & PARTN_MASK].start_sect;
               SELECT_DRIVE(hwif, drive);
               return (DRIVER(drive)->do_request(drive, rq, block));
       }
---------------------------------------------------------------------------------------------------

So, in the case of a partitioned disk it is only at this very low level that we add in the starting sector of the partition in order to get an absolute sector.
The first actual port access happened already:

---------------------------------------------------------------------------------------------------
       #define SELECT_DRIVE(hwif,drive) \
               OUT_BYTE((drive)->select.all,
hwif->io_ports[IDE_SELECT_OFFSET]);
---------------------------------------------------------------------------------------------------

but this do_request function must do the rest. For a disk it is defined in ide-disk.c, in the ide_driver_t idedisk_driver, and the function turns out to be do_rw_disk().

---------------------------------------------------------------------------------------------------
       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)&0x0f)|drive->select.all,IDE_SELECT_REG);
               } 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, &read_intr, WAIT_CMD, NULL);
                       OUT_BYTE(WIN_READ, IDE_COMMAND_REG);
                       return ide_started;
               }
               ...
       }
---------------------------------------------------------------------------------------------------

This fills the remaining control registers of the interface and starts the actual I/O. Now ide_set_handler() sets up read_intr() to be called when we get an interrupt. This calls ide_end_request() when a request is done, which calls
end_that_request_first() (which calls bh->b_end_io() as promised earlier) and end_that_request_last() which calls
blkdev_release_request() which wakes up whoever waited for the block.


[目录]


lisolog文章检索

[目录]


index


索引的内容:
我比较喜欢的帖子. 和精华收藏多有冲突.


索引的使用:
支持分类, 从这里找帖子, 然后分类,要比在论坛里搜索方便. 有这一点作用, 也不枉我遍历一次论坛.( 折叠起来看,不然太乱了)



索引的更新:
我们记录了索引更新的时间. 下一次更新时, 比方说一个月后, 只需选择显示一个月内发表的文章. 这样就不会丢掉文章. 但是,有可能重复, 因为新的回应会使帖子位置前移.这个问题这样解决:凡是我收集的文章统统加入我的收藏夹, 这样,更新时我只向索引里加入那些可以加入我的收藏夹的文章.



索引的局限:
水平有限, 不免错漏. 我尽力保留有价值的帖子. 不敢说没有在索引中的帖子就没有价值.但我一直在努力.

编辑者: hyl (07/12/02 13:56)

[目录]


list1


关于faq----征求建议和合作者

请教linux内核版本2.0.35的进程切换

诚征版主,祝内核版越办越好!

编译内核时有很多东东不明白什么含义,哪位知道什么地方可以找到比较全面的资料

高手是怎么编译内核的啊?

编译内核之一

编译内核之三

编译内核之四

编译内核之五

编译内核之六(后记)

大家对NeXT,BeOS,Darwin这些变种如何看待,似乎国内很少有人谈及?

编译内核之二

提问:the STRUCTURE of Linux?

编译内核时,在哪部分把PCMIA卡编译掉?

微内核?进程调度?

linux的非微内核一直受竞争对手的非议,请问未来linux的发展在这方面有什么计划?(null)

一点题外话

书上说段页式内存管理是最好的内存管理方式,但LINUX的内存管理使用的是页式,为什莫?

1f是指什么,好象并没有1f的标签

编译内核是否只是简单的make config,若不是,请给我详细步骤。

那位大侠知道如何记录通过ipchains网关的数据报

要怎样建立放火墙

模块程序却出错如下:invalid parameter parm_a

块设备驱动程序的注册似乎都是通过调用register_blkdev(),

调用了netif_rx 函数。

请问tunable parameter

关于进程数

如何编写自己的设备驱动程序,又如何在C语言中调用

where the kernel start? why I can not find the function 'main()'?

请问可不可以在Linux下改网卡的硬件地址(将每个发送出去的数据包的硬件地址改为特定的值)?

编译的时候他说我的最后一行:missing seprator

lilo怎么改?image=? initrd

init在哪儿

__asm__是干什么的?

编译setup.S为什么有一大堆错误

想让内核将printk输出到messages文件

get_current(void)

在内核态,进程如何访问用户态空间的数据?

Bug大侠,该如何研究Linux的源代码

head.s中调用setup_paging时,内存0x1000起开始的几页全被清零,岂不是将内核代码head.s部分覆盖了,这是怎么回事?

内核首先读入内存0x10000处,但后又移至0x1000处,这样岂不是与内核页目录swapper_pg_dir地址冲突吗

inw()/outw()、inl()/outl(),其中b、w、l各是什么意思

如何安装3块网卡,每个网卡在一个网段

要写一个PCI卡(自制的)driver

加载modules时就提示有些目标文件中存在unresolved simbol

linux -- driver的编写 -- file_operations

装载lilo时会显示:“LILO:”,请问如何改变这个显示

关于BluePoint2.0的几个烂点:

include头文件modversions.h时,一般用什么条件?为什么我用的时候它总是和#include asm/uaccess.h冲突

用insmod装载模块时,出现了unresolved symbol

Linux的整体式结构决定了其内核的高效性

我用命令 mount -t vfat /dev/hda1 /mnt/c 发现,我的linux好象,不支持vfat

如何查看当前内核的配置参数

我们也做嵌入式,欢迎一起创业

kmalloc(),返回的地址不用设置页表,而vmalloc()需要。 这说明什么

__get_free_pages()返回的是物理地址还是虚拟地址

分析do_fork chldreg指针的赋值的问题小弟感觉很糊涂

linux -- driver -- __put_user

linux -- driver -- ioctl

How can I do a Linux Boot/Root Disk

编写驱动程序时,需要将硬件的物理地址为虚地址. LINUX内核如何保证这种影射对每个进程都是有效的.

如何在Windows或者DOS下编译内核

内核过程所允许使用的堆栈空间有多大

system.map到底有什么用

想利用时钟中断,想自己加入一些代码形成自己的中断服务程序.

用signal 的确可以做当异步地运行某个函数, 信号是否是以中断的形式运行的

起动盘为什么mount不上去

看不出head.s是如何调用start_kernal()函数的

start_kernel()中我怎么没有发现初始化网络部分的函数

head.s究竟是怎样调用start_kernel()函数的呢

Linux是不是对中断控制器重新编程过了

系统初始化

阅读setup.S原程序时,对下面进入保护模式程序段有点不理解

kernel_thread()是用来产生init进程的,然后由init全权处理进程,它怎么会初始化网络呢

段描述符高速缓存寄存器和描述符投影寄存器有什么区别

段描述符中有一位p用来区分此段是否在内存中,问题是若一段中部分叶在内存,部分不在内存,此位如何赋值

如何有效阅读内核代码?

init 进程启动之后,所有其他进程就由INIT进程全权处理。这时可以说系统内核已经完全启动起来吗?

在head.s-->start_kernel()-->启动init这个过程我还没有发现内核调用调度函数

能不能将内核的地址空间动态映射到用户空间的0xc0000000-0xffffffff 上去

已分配并不再使用的堆栈页面是对换到交换空间中还是直接被系统回收

Why I can't boot linux form fd(volume 1.722M) but fd(volume 1.44) can do?

调入系统模块到0x1000:0处时,为虾米还要判断es为64k对齐 为什么还要调用kill_motor

Pentium CPU CR0中的WP位是干什么用的?

CPU从用户的特权级3进入到内核的特权级0,请问这时是CPU如何完成这个中断指令的?

setup.S的bootsect_helper程序时,对于bios的15h中断的87号移低端内存到高端内存的参数有点不明白

do_mmap()函数 off&~PAGE_MASK的含义是什么?mm->map_count 是何含义?mm->locked_vm不是被锁定的vm的个数吗?

Oops是什么意思

为什么要去分析内核 我是菜鸟,但我是鹰的后代

setup.S中为什么需要置A20线

setup.S中移动剩余setup代码区的一个问题

那么当系统物理内存为最大值4G时,内核似乎只能管理它自已地址空间中的1G? 应用程序0-0x8048000有何作用?

pmd是指什麽

vfork的原意是什么

东东太多,我不知从哪下手!

8295A的断口号是怎么分配的

一篇ELF格式的详细说明

SYMBOL_NAME(...)和__asm__volatile(...)的功能是什么呢?

Linux中,局部描述符表LDT有何用呢?

__pa(x)是干什么用的

内核不使用虚拟内存,但是却把0xc0000000-4g的地址做为内核保留地址,这是怎么一会事

vmalloc分配的内存能否被swap out

ping的源代码

MEM_ALT_K是什么

__volatile__是什么功能

将一个极小的GUI机制引入内核可否?

Linux console font operation

在共享库定义共享段

Linux交换内存的一个缺陷

do_page_fault部分时,对里边提及的Pentium cpu缺陷(f00fc7c8冻结指令)很迷惑

解读vmlinux.lds.S

fixmap_init()函数是用来干虾米的?

lcall7 读trap_init()

paging_init()中的end_mem = (end_mem + ~mask) & mask;

static定义的变量放于哪个段

_edata和_end在哪儿定义

如何往内核加载模块

勇敢地蜕去你的陈年旧皮!

do_IRQ()中断号错误码的正负问题

build_irq"#"前缀

编译内核VFS:can't open root device 08:0a

mem_map结构数组中每个页面的age值是在什么时候更新的

GCC为2.96版,这是个非稳定的版本

说说用户进程的页面切换

kernel_threadregs.esp由从何而来呢?

Too many open files"问题

3g的虚拟内存到物理内存的映射是否都在task_struct中

为什么要有8M的隔离带?

为何总是报错VFS:unable to mount root fs on 08:01?

\linux\kernel\init\main.c开始的部分其偏移地址是否都被编译为从大于3G开始

lds定义了内核映象所有符号从PAGE_OFFSET(3G)处开始编址

Linux目前的体系只能管理2G物理内存?

编译好的内核为什么要要经过压缩?

系统状态保存在当前进程任务结构和内核堆栈中。进程间的切换点都在schedule()中

setup_arch()函数是如何得到命令行参数的

进程数据结构页面是如何保证不被换出内存的?

问_end的定义

mount和insmod一个模块的区别?网卡的混杂模式是什么意思?

mmap用于进程间文件共享,SYSV IPC用于进程间内存共享

当当前进程current的调度策略是FIFO时,其时间片current.counter为何没有重新赋值

加载模块时,想给参数赋值

开机后不要人干与就直接可起动我的应用

FIFO策略进程的时间片(counter)没有重新赋值

如何使linux进程的堆栈是不可运行的?

可重入 和 ret_from_intr

FIFO的进程其时间片在schedule()中被忽视而没有重新赋值.请问这是否会导致它被频繁的重新调度呢?

推荐内核分析风格

__asm__ __volitile__

我的看法

如何编程接收ppp0上的裸IP包?

PS/2鼠标工作原理和硬件编程的技术资料

请教如何读linux的Kernel

模块的版本相关性指什么?

当物理内存超过3G时就管理不了?

lcall7的入口处比system_call多压栈了一项pushfl

原代码看的工具lxr是怎么用的?

调用_free_page()后,该内存页真的被释放了吗

free_area_init()中bitmap_size 是否有问题?

对try_to_swap_out()的一点改进

Where is file for mapping kernel virtual address

有没有关于gunzip()的解压算法

gunzip()函数在解压缩piggy.o(真正的内核)时,是如何定位或寻址到piggy.o的内存地址的呢

堆中内存是如何分配的

这里的set_pte使我很疑惑,为什么用的不是物理地址,而是加上0xC0000000的虚拟地址呢

编译时决定virtual address吗?

boot.S, setup.S ... is running with real-mode?Need not mmu settings?

idle是内核线程,init线程已转变为普通进程

a paper about debugger

由于ELF中允许用.section自定义段

trampoline乱谈

SMP不太熟悉,有几个入门性的问题

如何在内核中获得键盘和鼠标的消息

引用内核中变量求助 EXPORT_SYMBOL()宏添加相应内核符号

outb_p :where define?what does "%w1" mean ?

Linux太难学了

读kmem_cache_estimate ()这个函数

kmem_cache_estimate 中的 L1_CACHE_ALIGN

slab分配器的设计思想

page aligned 是什么意思

用nice等命令设定的进程优先级有什么确切的含义

模块中的全局符号自动添加到系统符号表中,模块中不能使用EXPORT_SYMBOL(), 可用EXPORT

在内核态与用户态转变时,地址映射是相同的,堆栈区域不同

linux核心程序中怎样打开设备呢

怎样调试内核???gdb行吗?

硬件内存在系统内存空间中的映射问题

和malloc,calloc一样,一般是用sbrk系统调用实现的

Linux头文件中定义了进行串操作的函数,驱动程序可以使用它们来获得比C语言写的循环更好的性能

readahead使用的各个变量的意义

About kernel stack

section __ex_table,"a"和.previous 以及.fixed

请教各位如何使用SYSCTL的问题

System Call is a limitation??

About GET_CURRENT

Linux的TCP/IP协议栈阅读笔记(1)

Linux的TCP/IP协议栈阅读笔记(2)

Linux的TCP/IP协议栈阅读笔记(3)

Linux的TCP/IP协议栈阅读笔记(4)

Linux的TCP/IP协议栈阅读笔记(5)

Linux的TCP/IP协议栈阅读笔记(6)

Linux的TCP/IP协议栈阅读笔记(7)

About the Linux Kernel Analysis Book

Linux的TCP/IP协议栈阅读笔记(8)

请大虾们推柬一下读核工具

请问内核2.4版本的zone分配器的设计思想

可否在内核中进行截短文件操作?

在模块中调用系统调用

请问怎样正确查找函数原型?

MTRR是什么?

Linux-2.4.0网络部分改变

About multiple txt segment in an elf

建议阅读linux device drivers

模块化编程可否替换所有的系统调用

How to release a Module by itself ?

Linux设备驱动程序勘误表(部分)

内核中替代realloc()函数 的具体实现方法

什么是NR?

MAP_FIXED是什么固定映射?

正交持续性 自反系统

为何要保存flags? 只用cli()和sti()不可以么?

如何写直接读写硬盘的驱动程序

这种kmalloc,优先级参数应为GFP_AUTOMIC?

因为"Hello,World"在内核段中,使用段超越试试看

##是文本连接运算符

详细介绍slab的linux实现的文章连接

Linux防火墙程序设计

编译内核后,一定要重新链接System.map到新的System.map吗?

tty 到底是谁的abbreviation

Linux 模块调度问题和抢占

about EXPORT_SYMBOL

什么叫映象文件

mm/memory.c和mm/vmalloc.c各负责什么功能

怎样把自行编写的设备驱动程序添加到Linux核心中

谁知道netstat下的TIME_WAIT如何产生和避免

内核代码中经常使用固定数组而不是链表是为了编程简单?

用ioperm申请要存取的端口范围的访问权

TIME_WAIT状态有什么用

要先包含<linux/module.h> <linux/kernel.h>然后sleep_on_interruptible_timeout就正常了,不会崩掉了

函数init()的最后执行了 execve()函数,为什么内核代码没有被execve()所创建的新进程覆盖掉

dput()和dget()

个时间片大小是固定的吗?

kmem_cache_grow()开头的一串标志检测也让我不理解
[目录]


list2



Shaper是一个限制网络速率的虚拟网络设备

jmpi go,INITSEG 是什么意思?

关于进程的flag,以及调度的一些概念

为何在 console_init()之后还不能打开/dev/console呀

do_wp_page

内存管理--end_mem解读

Unable to handle kernel paging request at virtual address...是从哪个模块报出来的?

按照原来配置重新编译2.4.0,重启后,屏幕显示:Uncompressing Linux ...Ok,booting the kernel.后就死机了

如何研究内核

有关kdev_t结构与次设备号的问题?

内存管理--free_area结构解读

内核地址手工转换,多是在填写页表时用到

进程管理--在时钟中断处理中为什么没有调用schedule函数

内存管理--memmap解读

请教Gcc源码的阅读问题

Unable to handle kernel paging request at virtual address....

Linux源代码讨论专用线索

我所看到的switch过程以及我的理解

我实现了一个进程切换方法

VFS: can't mount root filessystem . 这是怎么回事儿

内核栈

head.s中的LGDT装入gdt_descr处的内容...

内核空间在0xc0000000之上,但是如何使内核程序中访问的变量等的线形地址能够达到这个范围之上呢(因为段描述符的基地址是0),这是怎么做到的?(lds?)

汇编语言的语法

增加系统调用

块设备驱动,DMA内存,IDE硬盘的预读

traceroute的问题

在用户空间编写驱动程序

内核中的高端内存选项是怎么回事

在内核空间访问用户空间的问题

在地址0000开始的作了个中断向量表,这个是bios引导后实现的,还是dos获得系统控制权利后才做的事情??

中国的操作系统为什么停滞不前

在文件系统中struct dentry 是用来描述什么?

在x86平台上,io空间跟内存空间是分别编址的吗

Heap and Internal Fragmentation

文档最新动向 3月5日

/dev/ram,/dev/tty1等等,这些i节点是如何被创建的

消息可以代替信号?

About brk value, malloc, and heap

About contigeous virtual memory

About brk explaination from book.

在linux中,是怎么实现动态连接库的共享?地址和重入

raw disk I/O 的资料

todo & 遗留问题 & 计划介绍

一些资源

专题认领

源代码学习专题认领

编译过多次,但还是有些问题不能理解

外部中断的驱动怎么知道自己的3.硬件产生哪个中断?

对bottom half概念的一点理解

进程切换的时机


http://www.xfree86.org/
ftp://metalab.unc.edu/
http://www.xfree.org/FAQ/
http://www.xfree.org/#resources/
http://www.kde.org/
http://www.qt.org/
http://www.gnome.org/
http://www.gtk.org/
http://www.enlightenment.org/
http://www.opengroup.org/openmotif/
http://www.lesstif.org/
http://www.windowmaker.org/
http://www.gnustep.org/
http://www.itresearch.com/
ftp://ftp.funet.fi/pub/Linux/PEOPLE/Linus/SillySounds/
http://members.xoom.com/gnulix_guy/geek-gourmet/
http://www.csustan.edu/bazaar/


init process

在理解linux虚存的时候,我确总有些绕不过来

希望解决:3.硬件产生哪个中断的判别问题

Makefile 初探

所有的进程都公用_LDT(0)?

BUILD_IRQ宏

虚存难绕

Makefile解读之二

进程陷入内核时CR3的内容会改变吗?如果不变,如何存取内核空间呢?

barrier()的作用

Makefile解读之三: 模块的版本化处理

LDT:有点眉目了

系统调用流程

LINUX的系统内核空间的保护

Makefile解读之四: Rules.make的注释

HELP! printk() does not work in device module

as86汇编语言的语法说明

中断嵌套的问题

问题犹在:BUILD_COMMON_IRQ的宏展开

中断的部分代码解读

内核初起时如何从核心态进入用户态?

ASM格式简介

增加系统调用时的问题,虽已解决,但有的地方不太明白。

8259A的工作原理

Linux下的jiffies是10ms吗?

fork进程的学习

netfilter.c剖析1

getpid()

netfilter各个HOOK的关系

分析sockfs套接字文件系统

netfilter剖析2

backlog field in sock struct

所有进程在内核态的地址空间是一致的,可以相互访问的么

外设中的目录项(以EXT2为例)和内存中的"目录项"的比较

关于文件系统的安装与访问

Kernel 2.4中bottom half好象已经演化到soft-interrupt了

从系统调用open看源码

分析内核对gzip压缩文件进行解压的方法

sock 中zapped成员表示什

ip_tables.h分析

initrd 是干什么用的?

内核对以太网设备的探测过程

ISA网卡驱动程序的探测过程

sock结构的链接问题

read_lock()和write_lock()的过程描述如下

UNIX系统技术内幕》的第七章,介绍了自旋锁,看不懂他的改进

ISA网卡驱动程序发送和接收过程

EXT2的超级块与组描述符

新兵笔记--ULK(C2) beta版 Segmentation in Linux

网络包的排队发送过程

Linux secret.... (maybe)[Cross post]

Export了怎么还是无法resolve?

新兵笔记--ULK(Understanding the Linux Kernel) 序

关于netfilter的一点问题

ip_tables.c中组织规则的方式

内核打印的限速函数 net_ratelimit()

Confirm SA_INTERRUPT

trap和中断有什么不同啊

假如我要把PLT映射到内存的低端

Linux的硬件地址解析过程

inode和block之间有什么联系和区别

ip_tables.c的防火墙规则处理