探究Linux的奇妙之旅

原创 2016年06月01日 00:34:33

内容概要

本文主要探究linux 0.11的一些神奇的东西

理解schedule函数的调用

process 0 第一次调度和后面调度,schedule运行的机制是不同的。

    void main(void)     /* This really IS void, no error here. */
    {           
    ......
    /*
     *   NOTE!!   For any other task 'pause()' would mean we have to get a
     * signal to awaken, but task0 is the sole exception (see 'schedule()')
     * as task 0 gets activated at every idle moment (when no other tasks
     * can run). For task0 'pause()' just means we go check if some other
     * task can run, and if not we return here.
     */
        for(;;) pause();
    }

当进程0到达main函数的最后的循环之后,将通过系统调用,调用系统函数_sys_pause,其中调用schedule,

        int sys_pause(void)
        {
            current->state = TASK_INTERRUPTIBLE;
            schedule();
            return 0;
        }

查找到刚刚创建的进程1处于就绪状态,在schedule最后switch_to(1). 在switch中发生了什么呢?

        /*
     *  'schedule()' is the scheduler function. This is GOOD CODE! There
     * probably won't be any reason to change this, as it should work well
     * in all circumstances (ie gives IO-bound processes good response etc).
     * The one thing you might take a look at is the signal-handler code here.
     *
     *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
     * tasks can run. It can not be killed, and it cannot sleep. The 'state'
     * information in task[0] is never used.
     */
    void schedule(void)
    {
        int i,next,c;
        struct task_struct ** p;

    /* check alarm, wake up any interruptible tasks that have got a signal */

        for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
            if (*p) {
                if ((*p)->alarm && (*p)->alarm < jiffies) {
                        (*p)->signal |= (1<<(SIGALRM-1));
                        (*p)->alarm = 0;
                    }
                if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
                (*p)->state==TASK_INTERRUPTIBLE)
                    (*p)->state=TASK_RUNNING;
            }

    /* this is the scheduler proper: */

        while (1) {
            c = -1;
            next = 0;
            i = NR_TASKS;
            p = &task[NR_TASKS];
            while (--i) {
                if (!*--p)
                    continue;
                if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
                    c = (*p)->counter, next = i;
            }
            if (c) break;
            for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
                if (*p)
                    (*p)->counter = ((*p)->counter >> 1) +
                            (*p)->priority;
        }
        switch_to(next);
    }

ljmp 0 \n\t这个得查看IA-32手册,就会发现,IA-32架构中允许同种特权级之间相互切换,但是不同特权级之间切换是不允许的。但是0特权级的进程0是如何切换到3特权级的进程1呢?就要引入’门’的概念了。具体参见手册.门是允许点到点的,不同特权级之间的切换。

那么当进程1运行过之后,再次由进程0切换到进程1时,由于进程切换都是0特权级,此时,此时就不需要经过什么门了,IA32是允许同特权级之间进行进程切换的。

switch_to

    #define switch_to(n) {\
    struct {long a,b;} __tmp; \
    __asm__("cmpl %%ecx,_current\n\t" \
        "je 1f\n\t" \
        "movw %%dx,%1\n\t" \
        "xchgl %%ecx,_current\n\t" \
        "ljmp %0\n\t" \
        "cmpl %%ecx,_last_task_used_math\n\t" \
        "jne 1f\n\t" \
        "clts\n" \
        "1:" \
        ::"m" (*&__tmp.a),"m" (*&__tmp.b), \
        "d" (_TSS(n)),"c" ((long) task[n])); \
    }

这里涉及到TSS的定义和GDT LDT管理结构

    #define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
    #define FIRST_TSS_ENTRY 4

每个描述符占8个字节,第一个状态段是第四个,所以 <<3,得到第一个任务描述符在GDT中的位置,
而每个任务使用一个tss和ldt,占16字节,所以<<4,两者相加得到任务n的tss在GDT中的位置,写入EDX寄存器。
另外ECX指向要切换过去的新任务task[n]。

现在开始理解代码,首先声明了一个_tmp的结构,这个结构里面包括两个long型,32位机里面long占32位,声明这个结构主要与ljmp这个长跳指令有关,这个指令有两个参数,一个参数是段选择符,另一个是偏移地址,所以这个_tmp就是保存这两个参数。再比较任务n是不是当前任务,如果不是则跳转到标号1,否则交互ecx和current的内容,交换后的结果为ecx指向当前进程,current指向要切换过去的新进程,在执行长跳,%0代表输出输入寄存器列表中使用的第一个寄存器,即m"(*&__tmp.a),这个寄存器保存了*&__tmp.a,而_tmp.a存放的是32位偏移(对应EIP),_tmp.b存放的是新任务的tss段选择符(对应CS),长跳到段选择符会造成任务切换,这个是x86的硬件原理。"d" (_TSS(n)),"c" ((long) task[n]));

缓冲区

Linux0.11 中没有实现预读和预写功能,而是将预读功能转化为普通读取。
其中缓冲区的理解要找到如下数据结构:
1. buffer head

    struct buffer_head {
        char * b_data;          /* pointer to data block (1024 bytes) */
        unsigned long b_blocknr;    /* block number */
        unsigned short b_dev;       /* device (0 = free) */
        unsigned char b_uptodate;
        unsigned char b_dirt;       /* 0-clean,1-dirty */
        unsigned char b_count;      /* users using this block */
        unsigned char b_lock;       /* 0 - ok, 1 -locked */
        struct task_struct * b_wait;
        struct buffer_head * b_prev;
        struct buffer_head * b_next;
        struct buffer_head * b_prev_free;
        struct buffer_head * b_next_free;
    };
  1. hash_table
        struct buffer_head * hash_table[NR_HASH];
        #define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
        #define hash(dev,block) hash_table[_hashfn(dev,block)]
  1. request
        /*
         * Ok, this is an expanded form so that we can use the same
         * request for paging requests when that is implemented. In
         * paging, 'bh' is NULL, and 'waiting' is used to wait for
         * read/write completion.
         */
        struct request {
            int dev;        /* -1 if no request */
            int cmd;        /* READ or WRITE */
            int errors;
            unsigned long sector;
            unsigned long nr_sectors;
            char * buffer;
            struct task_struct * waiting;
            struct buffer_head * bh;
            struct request * next;
        };

READA and WRITEA

    static void make_request(int major,int rw, struct buffer_head * bh)
    {
        ......
        /* WRITEA/READA is special case - it is not really needed, so if the */
        /* buffer is locked, we just forget about it, else it's a normal read */
        if (rw_ahead = (rw == READA || rw == WRITEA)) {
            if (bh->b_lock)
                return;
            if (rw == READA)
                rw = READ;
            else
                rw = WRITE;
        }
        if (rw!=READ && rw!=WRITE)
            panic("Bad block dev command, must be R/W/RA/WA");
        lock_buffer(bh);
        if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) {
            unlock_buffer(bh);
            return;
        }
        ......
    }

那么什么是预读预写呢?参考Linux内核的文件预读

系统调用

        fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
        sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
        sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
        sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
        sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
        sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
        sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
        sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
        sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
        sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
        sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
        sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
        sys_setreuid,sys_setregid };

引用

Linus
Linux官网
Linux 内核设计的艺术

版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

NSArray的奇妙探索之旅(深入探究NSArray)

本文翻译自The Amazing Adventures of NSArray 译者注:原文对NSArray进行了深入的探究,挖掘了许多令人不可思议的东西,但也有那么些错误的地方(当然只是现在运行起...

产品经理与需求文档的一场奇妙之旅

产品经理与需求文档的一场奇妙之旅1.项目执行过程中问题出现在哪里? 需求评审需严谨,多次评审敲定主要需求和细致需求 业务需求明确的需求文档 虽是类似项目,但不可完全照搬上一个项目需求 讲师说的: 开发...

我是如何成为一名python大咖的?

人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..

Eclipse-ECF 感受奇妙的协同编程之旅

很久就对协同工作模式感兴趣,也是在前年,发现eclipse有个开源项目叫cola, 它可以提供令人感受奇妙的协同编程模式,但是当时这个项目还不成熟,处于试验阶段,想体验下协同编程需要很复杂的配置,如今...

EXT的奇妙之旅

初次知道EXT还是通过看一个同事以前做的项目,当时他们用EXT做了一个桌面版的系统,看到就一下被吸引了,心想:"原来WEB办也可以做的这么酷",后来就买了一本有关EXT的书学习,发现EXT有好多面向对...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)