用大白话说操作系统(十二)

上次我们讲到把进程进行fork,把大部分的东西进行复制,然后对个性化的东西进行赋值,他们分别指向不同的线性地址空间,但是指向相同的物理地址空间。

写时复制

fork函数只复制页表,不复制物理地址空间。这样如果只有读操作,完全没有影响,但是发生写操作的时候就会很麻烦。
有写操作,再复制物理内存,就叫做写时复制。
上面的逻辑是通过中断做到的,具体是缺页中断

int copy_page_tables(...) {
    ...
    // 源页表和新页表一样
    this_page = *from_page_table;
    ...
    // 源页表和新页表均置为只读
    this_page &= ~2;
    *from_page_table = this_page;
    ...
}

缺页中断的中断号是0x14.

void do_page_fault(..., unsigned long error_code) {
    ...   
    if (error_code & 1)
        do_wp_page(error_code, address, current, user_esp);
    else
        do_no_page(error_code, address, current, user_esp);
    ...
}

根据中断异常码error_code的不同,会有不同的逻辑。

当 error_code 的第 0 位,也就是存在位为 0 时,会走 do_no_page 逻辑,其余情况,均走 do_wp_page 逻辑。

void do_wp_page(unsigned long error_code,unsigned long address) {
    // 后面这一大坨计算了 address 在页表项的指针
    un_wp_page((unsigned long *)
        (((address>>10) & 0xffc) + (0xfffff000 &
        *((unsigned long *) ((address>>20) &0xffc)))));
}

void un_wp_page(unsigned long * table_entry) {
    unsigned long old_page,new_page;
    old_page = 0xfffff000 & *table_entry;
    // 只被引用一次,说明没有被共享,那只改下读写属性就行了
    if (mem_map[MAP_NR(old_page)]==1) {
        *table_entry |= 2;
        invalidate();
        return;
    }
    // 被引用多次,就需要复制页表了

    new_page=get_free_page();
    mem_map[MAP_NR(old_page)]--;
    *table_entry = new_page | 7;
    invalidate();
    copy_page(old_page,new_page);
}

// 刷新页变换高速缓冲宏函数
#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0))

在这里插入图片描述
我们可以看到这个时候的内存是被引用了两次的,所以当其中一个进程发生写操作时,会将页面进行复制,然后页面属性变成可读写。
在这里插入图片描述
如果这时候再对页面进行写操作,这时这个页面的引用次数是1,就不用进行复制了,只需要改变页面属性为可读写就可以了。
在这里插入图片描述
缺页中断里面除了do_wp_page,还有一个是页表项的存在位P为0时触发的do_no_page.这个是和进程按需加载内存相关如果还没有加载到内存,就会将这个函数将磁盘的数据复制到内存里。

init()

void main(void) {
    ...
    move_to_user_mode();
    if (!fork()) {
        init();
    }
    for(;;) pause();
}

我们已经通过fork函数创建出一个进程1.父进程的返回子进程的ID,子进程返回0,init函数只有进程1才会执行。

void init(void) {
    int pid,i;
    setup((void *) &drive_info);
    (void) open("/dev/tty0",O_RDWR,0);
    (void) dup(0);
    (void) dup(0);
    if (!(pid=fork())) {
        open("/etc/rc",O_RDONLY,0);
        execve("/bin/sh",argv_rc,envp_rc);
    }
    if (pid>0)
        while (pid != wait(&i))
            /* nothing */;
    while (1) {
        if (!pid=fork()) {
            close(0);close(1);close(2);
            setsid();
            (void) open("/dev/tty0",O_RDWR,0);
            (void) dup(0);
            (void) dup(0);
            _exit(execve("/bin/sh",argv,envp));
        }
        while (1)
            if (pid == wait(&i))
                break;
        sync();
    }
    _exit(0);   /* NOTE! _exit, not exit() */
}

先来看第一行代码

 setup((void *) &drive_info);

drive_info是来自内存0x90080的数据,这部分是之前说的setup.s程序将硬盘1的参数信息都放在这里面了,包括柱面数,磁头数,扇区数等。
setup是个系统调用,会通过中断调用sys_setup函数。
sys_setup

int sys_setup(void * BIOS) {
   //硬盘基本信息的赋值操作
   hd_info[0].cyl = *(unsigned short *) BIOS;
   hd_info[0].head = *(unsigned char *) (2+BIOS);
   hd_info[0].wpcom = *(unsigned short *) (5+BIOS);
   hd_info[0].ctl = *(unsigned char *) (8+BIOS);
   hd_info[0].lzone = *(unsigned short *) (12+BIOS);
   hd_info[0].sect = *(unsigned char *) (14+BIOS);
   BIOS += 16;

   hd[0].start_sect = 0;
   hd[0].nr_sects = 
       hd_info[0].head * hd_info[0].sect * hd_info[0].cyl;
   
   struct buffer_head *bh = bread(0x300, 0);
   struct partition *p = 0x1BE + (void *)bh->b_data;
   for (int i=1;i<5;i++,p++) {
       hd[i].start_sect = p->start_sect;
       hd[i].nr_sects = p->nr_sects;
   }
   brelse(bh);
   
   rd_load();
   mount_root();
   return (0);
}

hd_info 是一个数组表示每个硬盘的参数的集合、
hd_i_struct表示硬盘的参数

struct hd_i_struct {
    // 磁头数、每磁道扇区数、柱面数、写前预补偿柱面号、磁头着陆区柱面号、控制字节
    int head,sect,cyl,wpcom,lzone,ctl;
};
struct hd_i_struct hd_info[] = {}

hd数组硬盘分区表

static struct hd_struct {
    long start_sect;//开始扇区
    long nr_sects;//总扇区数
} hd[5] = {}

在硬盘的第一个扇区的0x1BE偏移处,存储着硬盘的分区信息,只要到这个地方拿到数据就可以了。

int sys_setup(void * BIOS) {
    ...
    rd_load();
    mount_root();
    return (0);
}

其中 rd_load 是当有 ramdisk 时,也就是虚拟内存盘,才会执行。虚拟内存盘是通过软件将一部分内存(RAM)模拟为硬盘来使用的一种技术,一种小玩法而已,我们就先当做没有,否则很影响看主流程的心情。
mount_root 直译过来就是加载根,再多说几个字是加载根文件系统,有了它之后,操作系统才能从一个根开始找到所有存储在硬盘中的文件,所以它是文件系统的基石,很重要。
mount_root

void mount_root(void) {
    int i,free;
    struct super_block * p;
    struct m_inode * mi;

    for(i=0;i<64;i++)
        file_table[i].f_count=0;

    for(p = &super_block[0] ; p < &super_block[8] ; p++) {
        p->s_dev = 0;
        p->s_lock = 0;
        p->s_wait = NULL;
    }
    p=read_super(0);
    mi=iget(0,1);

    mi->i_count += 3 ;
    p->s_isup = p->s_imount = mi;
    current->pwd = mi;
    current->root = mi;
    free=0;
    i=p->s_nzones;
    while (-- i >= 0)
        if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))
            free++;

    free=0;
    i=p->s_ninodes+1;
    while (-- i >= 0)
        if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))
            free++;
}

硬盘中的文件系统格式是怎么样的
Linux-0.11中的文件系统是MINIX文件系统。
在这里插入图片描述
每一个块的大小是1024字节,也就是1KB,硬盘里的数据按照这个结构,安排在硬盘当中。
引导块就是我们最开头说的启动区。不是所有的硬盘都有启动区,但是还是预留出这个位置。
超级块描述整个文件系统的整体信息,有inode数量,块数量,第一个块在哪里。
inode位图和块位图表示后面inode和块的使用情况,和内存占用位图mem_map[]作用类似、
inode存放每个文件或目录的元信息和索引信息。
元信息文件类型、文件大小、修改时间等
索引信息大小为9的i_zone[9]块数组,表示这个文件或目录的具体数据占用了哪些块。
块数组里面0-6表示直接索引,7表示一次间接索引,8表示二次间接索引。
内存中用于文件系统的数据结构有哪些

struct file {
    unsigned short f_mode;
    unsigned short f_flags;
    unsigned short f_count;
    struct m_inode * f_inode;
    off_t f_pos;
};

void mount_root(void) {
    for(i=0;i<64;i++)
        file_table[i].f_count=0;
    ...
}

把64个file_table里的f_count清零。
这个file_table表示进程所使用的文件。f_count表示被引用的次数
file_table的索引就是通常所说的文件描述符

echo "hello">0

表示把hello字符串输出到0号文件

struct super_block super_block[8];
void mount_root(void) {
    ...
    struct super_block * p;
    for(p = &super_block[0] ; p < &super_block[8] ; p++) {
        p->s_dev = 0;
        p->s_lock = 0;
        p->s_wait = NULL;
    }
    ...
}

上述代码把一个数组super_block清零
super_block操作系统与一个设备以文件形式进行读写访问时就需要把这个设备的超级块信息放在这里。**通过这个超级块可以掌握这个设备的文件系统全局信息。

  • 读取硬盘的超级块信息到内存
  • 读取根inode信息
  • 把inode设置为当前进程当前工作目录和根目录
  • 记录块位图信息
  • 记录inode位图信息
    **至此为止,mount_root函数就全部结束了,sys_setup函数也全部结束了。
    我们继续看init函数
(void) open("/dev/tty0",O_RDWR,0);

好的,OK,现在我们就可以顺藤摸瓜,找到这个文件了,具体下次再说啦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神仙诙谐代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值