Lec 8 - Lab 5 Copy-on-Write Fork

Interrupt硬件部分

中斷對(duì)應(yīng)的場(chǎng)景很簡(jiǎn)單骄噪,就是硬件想要得到操作系統(tǒng)的關(guān)注莽鸿。例如網(wǎng)卡收到了一個(gè)packet山憨,網(wǎng)卡會(huì)生成一個(gè)中斷焦蘑;用戶通過(guò)鍵盤(pán)按下了一個(gè)按鍵盯拱,鍵盤(pán)會(huì)產(chǎn)生一個(gè)中斷.
3個(gè)差別: 異步,Interrupt handler與當(dāng)前運(yùn)行的進(jìn)程在CPU上沒(méi)有任何關(guān)聯(lián); 并行,設(shè)備handler 和 CPU 同時(shí)運(yùn)行. 設(shè)備編程,每個(gè)設(shè)備都有一個(gè)編程手冊(cè).


image.png

image.png

從左上角可以看出,我們有53個(gè)不同的來(lái)自于設(shè)備的中斷例嘱。這些中斷到達(dá)PLIC之后狡逢,PLIC會(huì)路由這些中斷。圖的右下角是CPU的核拼卵,PLIC會(huì)將中斷路由到某一個(gè)CPU的核.

  • PLIC會(huì)通知當(dāng)前有一個(gè)待處理的中斷
  • 其中一個(gè)CPU核會(huì)Claim接收中斷奢浑,這樣PLIC就不會(huì)把中斷發(fā)給其他的CPU處理
  • CPU核處理完中斷之后,CPU會(huì)通知PLIC
  • PLIC將不再保存中斷的信息

設(shè)備驅(qū)動(dòng)概述

管理設(shè)備的代碼稱為驅(qū)動(dòng).
UART設(shè)備的驅(qū)動(dòng)腋腮,代碼在uart.c文件中.如果我們查看代碼的結(jié)構(gòu)殷费,我們可以發(fā)現(xiàn)大部分驅(qū)動(dòng)都分為兩個(gè)部分,bottom/top低葫。

bottom

bottom部分通常是Interrupt handler.nterrupt handler并不運(yùn)行在任何特定進(jìn)程的context中详羡,它只是處理中斷

// handle a uart interrupt, raised because input has
// arrived, or the uart is ready for more output, or
// both. called from devintr().
void
uartintr(void)
{
  // read and process incoming characters.
  while(1){
    int c = uartgetc();
    if(c == -1)
      break;
    consoleintr(c);
  }

  // send buffered characters.
  acquire(&uart_tx_lock);
  uartstart();
  release(&uart_tx_lock);
}

top

top部分,是用戶進(jìn)程嘿悬,或者內(nèi)核的其他部分去調(diào)用的接口
比如:

// alternate version of uartputc() that doesn't 
// use interrupts, for use by kernel printf() and
// to echo characters. it spins waiting for the uart's
// output register to be empty.
void
uartputc_sync(int c)
{
  push_off();

  if(panicked){
    for(;;)
      ;
  }

  // wait for Transmit Holding Empty to be set in LSR.
  while((ReadReg(LSR) & LSR_TX_IDLE) == 0)
    ;
  WriteReg(THR, c);

  pop_off();
}

通常情況下实柠,驅(qū)動(dòng)中會(huì)有一些隊(duì)列(或者說(shuō)buffer),top部分的代碼會(huì)從隊(duì)列中讀寫(xiě)數(shù)據(jù)善涨,而Interrupt handler(bottom部分)同時(shí)也會(huì)向隊(duì)列中讀寫(xiě)數(shù)據(jù).
Interrupt handler來(lái)說(shuō)存在一些限制窒盐,因?yàn)樗](méi)有運(yùn)行在任何進(jìn)程的context中,所以進(jìn)程的page table并不知道該從哪個(gè)地址讀寫(xiě)數(shù)據(jù)钢拧,也就無(wú)法直接從Interrupt handler讀寫(xiě)數(shù)據(jù).

如何對(duì)設(shè)備進(jìn)行編程

操作系統(tǒng)需要知道這些設(shè)備位于物理地址空間的具體位置蟹漓,然后再通過(guò)普通的load/store指令對(duì)這些地址進(jìn)行編程。load/store指令實(shí)際上的工作就是讀寫(xiě)設(shè)備的控制寄存器源内。

例如葡粒,對(duì)網(wǎng)卡執(zhí)行store指令時(shí),CPU會(huì)修改網(wǎng)卡的某個(gè)控制寄存器,進(jìn)而導(dǎo)致網(wǎng)卡發(fā)送一個(gè)packet嗽交。所以這里的load/store指令不會(huì)讀寫(xiě)內(nèi)存卿嘲,而是會(huì)操作設(shè)備

Console是如何顯示出$ ls

$ 是Shell程序的輸出,而“l(fā)s”是用戶通過(guò)鍵盤(pán)輸入之后再顯示出來(lái)的.
實(shí)際上就是設(shè)備會(huì)將字符傳輸給UART的寄存器夫壁,UART之后會(huì)在發(fā)送完字符之后產(chǎn)生一個(gè)中斷.線路的另一端會(huì)有另一個(gè)UART芯片(模擬的)拾枣,這個(gè)UART芯片連接到了虛擬的Console,它會(huì)進(jìn)一步將$ 顯示在console上.

對(duì)于“l(fā)s”,當(dāng)你在鍵盤(pán)上按下一個(gè)按鍵盒让,UART芯片會(huì)將按鍵字符通過(guò)串口線發(fā)送到另一端的UART芯片.另一端的UART芯片先將數(shù)據(jù)bit合并成一個(gè)Byte梅肤,之后再產(chǎn)生一個(gè)中斷,并告訴處理器說(shuō)這里有一個(gè)來(lái)自于鍵盤(pán)的字符.

  • SIE(Supervisor Interrupt Enable)寄存器邑茄。這個(gè)寄存器中有一個(gè)bit(E)專門(mén)針對(duì)例如UART的外部設(shè)備的中斷.
  • SSTATUS(Supervisor Status)寄存器姨蝴。這個(gè)寄存器中有一個(gè)bit來(lái)打開(kāi)或者關(guān)閉中斷。每一個(gè)CPU核都有獨(dú)立的SIE和SSTATUS寄存器
  • SIP(Supervisor Interrupt Pending)寄存器撩扒。當(dāng)發(fā)生中斷時(shí)似扔,處理器可以通過(guò)查看這個(gè)寄存器知道當(dāng)前是什么類型的中斷吨些。

我們第一個(gè)外設(shè)是console搓谆,這是我們print的輸出位置。查看位于console.c的consoleinit函數(shù)度液。


image.png

uartinit函數(shù)位于uart.c文件拥峦。這個(gè)函數(shù)實(shí)際上就是配置好UART芯片使其可以被使用


image.png

運(yùn)行完這個(gè)函數(shù)之后笔呀,原則上UART就可以生成中斷了。但是因?yàn)槲覀冞€沒(méi)有對(duì)PLIC編程斩萌,所以中斷不能被CPU感知。最終屏轰,在main函數(shù)中颊郎,需要調(diào)用plicinit函數(shù)。下圖是plicinit函數(shù)霎苗。
void
plicinit(void)
{
  // set desired IRQ priorities non-zero (otherwise disabled).
  *(uint32*)(PLIC + UART0_IRQ*4) = 1;
  *(uint32*)(PLIC + VIRTIO0_IRQ*4) = 1;
}

代碼的第一行使能響應(yīng)UART的中斷姆吭,這里實(shí)際上就是設(shè)置PLIC會(huì)接收哪些中斷,進(jìn)而將中斷路由到CPU唁盏。
代碼的第二行設(shè)置PLIC接收來(lái)自IO磁盤(pán)的中斷内狸。
plicinit之后就是plicinithart函數(shù)。plicinit是由0號(hào)CPU運(yùn)行厘擂,之后昆淡,每個(gè)CPU的核都需要調(diào)用plicinithart函數(shù)表明對(duì)于哪些外設(shè)中斷感興趣。

void
plicinithart(void)
{
  int hart = cpuid();
  
  // set enable bits for this hart's S-mode
  // for the uart and virtio disk.
  *(uint32*)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ);

  // set this hart's S-mode priority threshold to 0.
  *(uint32*)PLIC_SPRIORITY(hart) = 0;
}

在plicinithart函數(shù)中刽严,每個(gè)CPU的核都表明自己對(duì)來(lái)自于UART和VIRTIO的中斷感興趣昂灵。

到目前為止,我們有了生成中斷的外部設(shè)備,我們有了PLIC可以傳遞中斷到單個(gè)的CPU倔既。但是CPU自己還沒(méi)有設(shè)置好接收中斷恕曲,因?yàn)槲覀冞€沒(méi)有設(shè)置好SSTATUS寄存器.
在實(shí)際運(yùn)行進(jìn)程之前,會(huì)執(zhí)行intr_on函數(shù)來(lái)使得CPU能接收中斷渤涌。intr_on函數(shù)只完成一件事情佩谣,就是設(shè)置SSTATUS寄存器,打開(kāi)中斷標(biāo)志位实蓬。

// enable device interrupts
static inline void
intr_on()
{
  w_sstatus(r_sstatus() | SSTATUS_SIE);
}

UART驅(qū)動(dòng)的top部分

如何從Shell程序輸出提示符“$ ”到Console.
在 init.c 的 main函數(shù)創(chuàng)建了一個(gè)代表Console的設(shè)備茸俭。這里通過(guò)mknod操作創(chuàng)建了console設(shè)備。因?yàn)檫@是第一個(gè)打開(kāi)的文件安皱,所以這里的文件描述符0调鬓。之后通過(guò)dup創(chuàng)建stdout和stderr。這里實(shí)際上通過(guò)復(fù)制文件描述符0酌伊,得到了另外兩個(gè)文件描述符1腾窝,2。最終文件描述符0居砖,1虹脯,2都用來(lái)代表Console。

if(open("console", O_RDWR) < 0){
    mknod("console", CONSOLE, 0);
    open("console", O_RDWR);
  }
  dup(0);  // stdout
  dup(0);  // stderr

盡管Console背后是UART設(shè)備奏候,但是從應(yīng)用程序來(lái)看循集,它就像是一個(gè)普通的文件。Shell程序只是向文件描述符2寫(xiě)了數(shù)據(jù)蔗草。

 else if(f->type == FD_DEVICE){
    if(f->major < 0 || f->major >= NDEV || !devsw[f->major].write)
      return -1;
    ret = devsw[f->major].write(1, addr, n);
  }

在filewrite函數(shù)中首先會(huì)判斷文件描述符的類型咒彤。mknod生成的文件描述符屬于設(shè)備(FD_DEVICE),而對(duì)于設(shè)備類型的文件描述符咒精,我們會(huì)為這個(gè)特定的設(shè)備執(zhí)行設(shè)備相應(yīng)的write函數(shù)镶柱。因?yàn)槲覀儸F(xiàn)在的設(shè)備是Console,所以我們知道這里會(huì)調(diào)用console.c中的consolewrite函數(shù)模叙。

void
consoleinit(void)
{
  initlock(&cons.lock, "cons");

  uartinit();

  // connect read and write system calls
  // to consoleread and consolewrite.
  devsw[CONSOLE].read = consoleread;
  devsw[CONSOLE].write = consolewrite;
}
//
// user write()s to the console go here.
//
int
consolewrite(int user_src, uint64 src, int n)
{
  int i;

  for(i = 0; i < n; i++){
    char c;
    if(either_copyin(&c, user_src, src+i, 1) == -1)
      break;
    uartputc(c);
  }

  return i;
}

這里先通過(guò)either_copyin將字符拷入歇拆,之后調(diào)用uartputc函數(shù)。uartputc函數(shù)將字符寫(xiě)入給UART設(shè)備向楼,所以你可以認(rèn)為consolewrite是一個(gè)UART驅(qū)動(dòng)的top部分查吊。uart.c文件中的uartputc函數(shù)會(huì)實(shí)際的打印字符。

void
uartputc(int c)
{
  acquire(&uart_tx_lock);

  if(panicked){
    for(;;)
      ;
  }
  while(uart_tx_w == uart_tx_r + UART_TX_BUF_SIZE){
    // buffer is full.
    // wait for uartstart() to open up space in the buffer.
    sleep(&uart_tx_r, &uart_tx_lock);
  }
  uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] = c;
  uart_tx_w += 1;
  uartstart();
  release(&uart_tx_lock);
}

在UART的內(nèi)部會(huì)有一個(gè)buffer用來(lái)發(fā)送數(shù)據(jù)湖蜕,buffer的大小是32個(gè)字符逻卖。同時(shí)還有一個(gè)為consumer提供的讀指針和為producer提供的寫(xiě)指針,來(lái)構(gòu)建一個(gè)環(huán)形的buffer.昭抒。如果讀寫(xiě)指針相同评也,那么buffer是空的炼杖,如果寫(xiě)指針加1等于讀指針,那么buffer滿了.

Shell是producer盗迟,所以需要調(diào)用uartputc函數(shù).因?yàn)樘崾痉?”是我們送出的第一個(gè)字符坤邪。所以代碼會(huì)走到while外面,字符會(huì)被送到buffer中罚缕,更新寫(xiě)指針艇纺,之后再調(diào)用uartstart函數(shù)。

// if the UART is idle, and a character is waiting
// in the transmit buffer, send it.
// caller must hold uart_tx_lock.
// called from both the top- and bottom-half.
void
uartstart()
{
  while(1){
    if(uart_tx_w == uart_tx_r){
      // transmit buffer is empty.
      return;
    }
    
    if((ReadReg(LSR) & LSR_TX_IDLE) == 0){
      // the UART transmit holding register is full,
      // so we cannot give it another byte.
      // it will interrupt when it's ready for a new byte.
      return;
    }
    
    int c = uart_tx_buf[uart_tx_r % UART_TX_BUF_SIZE];
    uart_tx_r += 1;
    
    // maybe uartputc() is waiting for space in the buffer.
    wakeup(&uart_tx_r);
    
    WriteReg(THR, c);
  }
}

uartstart就是通知設(shè)備執(zhí)行操作邮弹。首先是檢查當(dāng)前設(shè)備是否空閑黔衡,如果空閑的話,我們會(huì)從buffer中讀出數(shù)據(jù)腌乡,然后將數(shù)據(jù)寫(xiě)入到THR(Transmission Holding Register)發(fā)送寄存器盟劫。這里相當(dāng)于告訴設(shè)備,我這里有一個(gè)字節(jié)需要你來(lái)發(fā)送与纽。

與此同時(shí)侣签,UART設(shè)備會(huì)將數(shù)據(jù)送出。在某個(gè)時(shí)間點(diǎn)急迂,我們會(huì)收到中斷影所,因?yàn)槲覀冎霸O(shè)置了要處理UART設(shè)備中斷。接下來(lái)我們看一下袋毙,當(dāng)發(fā)生中斷時(shí)型檀,實(shí)際會(huì)發(fā)生什么冗尤。

UART驅(qū)動(dòng)的bottom部分

在trap.c的devintr函數(shù)中听盖,首先會(huì)通過(guò)SCAUSE寄存器判斷當(dāng)前中斷是否是來(lái)自于外設(shè)的中斷。如果是的話裂七,再調(diào)用plic_claim函數(shù)來(lái)獲取中斷皆看。

int
devintr()
{
  uint64 scause = r_scause();

  if((scause & 0x8000000000000000L) &&
     (scause & 0xff) == 9){
    // this is a supervisor external interrupt, via PLIC.

    // irq indicates which device interrupted.
    int irq = plic_claim();

    if(irq == UART0_IRQ){
      uartintr();
    } else if(irq == VIRTIO0_IRQ){
      virtio_disk_intr();
    } else if(irq){
      printf("unexpected interrupt irq=%d\n", irq);
    }

plic_claim函數(shù)位于plic.c文件中。在這個(gè)函數(shù)中背零,當(dāng)前CPU核會(huì)告知PLIC腰吟,自己要處理中斷,PLIC_CLAIM會(huì)將中斷號(hào)返回徙瓶,對(duì)于UART來(lái)說(shuō)毛雇,返回的中斷號(hào)是10。

// handle a uart interrupt, raised because input has
// arrived, or the uart is ready for more output, or
// both. called from devintr().
void
uartintr(void)
{
  // read and process incoming characters.
  while(1){
    int c = uartgetc();
    if(c == -1)
      break;
    consoleintr(c);
  }

  // send buffered characters.
  acquire(&uart_tx_lock);
  uartstart();
  release(&uart_tx_lock);
}
int
uartgetc(void)
{
  if(ReadReg(LSR) & 0x01){
    // input data is ready.
    return ReadReg(RHR);
  } else {
    return -1;
  }
}

為我們現(xiàn)在還沒(méi)有通過(guò)鍵盤(pán)輸入任何數(shù)據(jù)灵疮,所以UART的接受寄存器現(xiàn)在為空震捣。代碼會(huì)直接運(yùn)行到uartstart函數(shù),這個(gè)函數(shù)會(huì)將Shell存儲(chǔ)在buffer中的任意字符送出润樱。實(shí)際上在提示符“$”之后壹若,Shell還會(huì)輸出一個(gè)空格字符,write系統(tǒng)調(diào)用可以在UART發(fā)送提示符“$”的同時(shí)皂冰,并發(fā)的將空格字符寫(xiě)入到buffer中舌稀。所以UART的發(fā)送中斷觸發(fā)時(shí),可以發(fā)現(xiàn)在buffer中還有一個(gè)空格字符灼擂,之后會(huì)將這個(gè)空格字符送出壁查。
UART連接了兩個(gè)設(shè)備,一個(gè)是鍵盤(pán)剔应,另一個(gè)是顯示設(shè)備睡腿,也就是Console。

UART讀取鍵盤(pán)輸入

有時(shí)Shell會(huì)調(diào)用read從鍵盤(pán)中讀取字符峻贮。 在read系統(tǒng)調(diào)用的底層席怪,會(huì)調(diào)用fileread函數(shù)。在這個(gè)函數(shù)中纤控,如果讀取的文件類型是設(shè)備挂捻,會(huì)調(diào)用相應(yīng)設(shè)備的read函數(shù)。

int
consoleread(int user_dst, uint64 dst, int n)
{
  uint target;
  int c;
  char cbuf;

  target = n;
  acquire(&cons.lock);
  while(n > 0){
    // wait until interrupt handler has put some
    // input into cons.buffer.
    while(cons.r == cons.w){
      if(killed(myproc())){
        release(&cons.lock);
        return -1;
      }
      sleep(&cons.r, &cons.lock);
    }

    c = cons.buf[cons.r++ % INPUT_BUF_SIZE];

    if(c == C('D')){  // end-of-file
      if(n < target){
        // Save ^D for next time, to make sure
        // caller gets a 0-byte result.
        cons.r--;
      }
      break;
    }
    // copy the input byte to the user-space buffer.
    cbuf = c;
    if(either_copyout(user_dst, dst, &cbuf, 1) == -1)
      break;

    dst++;
    --n;

    if(c == '\n'){
      // a whole line has arrived, return to
      // the user-level read().
      break;
    }
  }
  release(&cons.lock);

  return target - n;
}

所以在某個(gè)時(shí)間點(diǎn)船万,假設(shè)用戶通過(guò)鍵盤(pán)輸入了“l(fā)”声怔,這會(huì)導(dǎo)致“l(fā)”被發(fā)送到主板上的UART芯片,產(chǎn)生中斷之后再被PLIC路由到某個(gè)CPU核,之后會(huì)觸發(fā)devintr函數(shù),devintr可以發(fā)現(xiàn)這是一個(gè)UART中斷慨亲,然后通過(guò)uartgetc函數(shù)獲取到相應(yīng)的字符,之后再將字符傳遞給consoleintr函數(shù)。

// read one input character from the UART.
// return -1 if none is waiting.
int
uartgetc(void)
{
  if(ReadReg(LSR) & 0x01){
    // input data is ready.
    return ReadReg(RHR);
  } else {
    return -1;
  }
}

// handle a uart interrupt, raised because input has
// arrived, or the uart is ready for more output, or
// both. called from devintr().
void
uartintr(void)
{
  // read and process incoming characters.
  while(1){
    int c = uartgetc();
    if(c == -1)
      break;
    consoleintr(c);
  }

  // send buffered characters.
  acquire(&uart_tx_lock);
  uartstart();
  release(&uart_tx_lock);
}

//
// the console input interrupt handler.
// uartintr() calls this for input character.
// do erase/kill processing, append to cons.buf,
// wake up consoleread() if a whole line has arrived.
//
void
consoleintr(int c)
{
  acquire(&cons.lock);

  switch(c){
  case C('P'):  // Print process list.
    procdump();
    break;
  case C('U'):  // Kill line.
    while(cons.e != cons.w &&
          cons.buf[(cons.e-1) % INPUT_BUF_SIZE] != '\n'){
      cons.e--;
      consputc(BACKSPACE);
    }
    break;
  case C('H'): // Backspace
  case '\x7f': // Delete key
    if(cons.e != cons.w){
      cons.e--;
      consputc(BACKSPACE);
    }
    break;
  default:
    if(c != 0 && cons.e-cons.r < INPUT_BUF_SIZE){
      c = (c == '\r') ? '\n' : c;

      // echo back to the user.
      consputc(c);

      // store for consumption by consoleread().
      cons.buf[cons.e++ % INPUT_BUF_SIZE] = c;

      if(c == '\n' || c == C('D') || cons.e-cons.r == INPUT_BUF_SIZE){
        // wake up consoleread() if a whole line (or end-of-file)
        // has arrived.
        cons.w = cons.e;
        wakeup(&cons.r);
      }
    }
    break;
  }
  
  release(&cons.lock);
}

字符會(huì)通過(guò)consputc片橡,輸出到console上給用戶查看。之后,字符被存放在buffer中舆吮。在遇到換行符的時(shí)候色冀,喚醒之前sleep的進(jìn)程敌卓,也就是Shell趟径,再?gòu)腷uffer中將數(shù)據(jù)讀出蜗巧。
所以這里也是通過(guò)buffer將consumer和producer之間解耦幕屹,這樣它們才能按照自己的速度,獨(dú)立的并行運(yùn)行挫鸽。如果某一個(gè)運(yùn)行的過(guò)快了,那么buffer要么是滿的要么是空的枫匾,consumer和producer其中一個(gè)會(huì)sleep并等待另一個(gè)追上來(lái)干茉。

Lab: Copy-on-Write Fork for xv6

這個(gè)lab還是非常耗時(shí)的等脂,因?yàn)殡y在他是第一次有了race condition。 如果一開(kāi)始沒(méi)有想清楚争涌,多個(gè)進(jìn)程并發(fā)的情況模软,就很容易陷入痛苦的debug中。但想清楚后继蜡,核心代碼非常簡(jiǎn)潔仅颇。
代碼分為2部分忘瓦。

第一部分境蜕,是對(duì)每個(gè)物理頁(yè)分配一個(gè)引用計(jì)數(shù)器汽摹。然后更新kalloc.c 每個(gè)函數(shù)逼泣,去正確配置他們,因?yàn)闀?huì)涉及多進(jìn)程并行操作氏仗,所以需要上鎖皆尔。


1700306424947.png

1700306445830.png

第二部分,是我們需要在uvmcopy中(fork調(diào)用)流炕,針對(duì)有寫(xiě)權(quán)限的頁(yè)每辟,進(jìn)行抹除父子頁(yè)上的寫(xiě)權(quán)限標(biāo)志。然后添加COW標(biāo)志挠将。同時(shí)增加該頁(yè)指向的物理地址的引用計(jì)數(shù)捐名。然后在發(fā)生page fault中斷成艘,或copyout時(shí),進(jìn)行寫(xiě)時(shí)復(fù)制拂酣。

1700306480744.png

1700306492500.png

1700306506697.png

optional challenge

Measure how much your COW implementation reduces the number of bytes xv6 copies and the number of physical pages it allocates. Find and exploit opportunities to further reduce those numbers.

我們可以看到上面的做法赵颅,保證了線程安全性,但是我們寫(xiě)一個(gè)函數(shù)去統(tǒng)計(jì)節(jié)約的頁(yè)面和字節(jié)復(fù)制的開(kāi)銷募寨,其實(shí)是不夠理想的拔鹰。我們先在kalloc里定義2個(gè)新函數(shù)。


1700306664943.png

創(chuàng)建一個(gè)新的系統(tǒng)調(diào)用


1700306700154.png

然后我們?cè)赨VMCOPY,可以去節(jié)約頁(yè)面復(fù)制開(kāi)銷决采,但是在copyonwrite的時(shí)候树瞭,是要把節(jié)約的開(kāi)銷給還回去晒喷。


1700306847476.png

我們可以看到在simpletest的時(shí)候,是節(jié)約了不少開(kāi)銷势决。但是在做three test的時(shí)候,我們不但沒(méi)有節(jié)約,反而還比之前付出了更多的頁(yè)面開(kāi)銷独柑。


1700307109412.png

這里進(jìn)一步的優(yōu)化空間就在于迈窟,當(dāng)引用計(jì)數(shù)降為1時(shí),我們依然保持了kalloc 加 kfree的策略群嗤。也就是說(shuō)我們有2個(gè)進(jìn)程菠隆,做fork的時(shí)候,假設(shè)節(jié)約了4頁(yè)狂秘。但是這4頁(yè)之后都被父進(jìn)程和子進(jìn)程寫(xiě)的話骇径。父子進(jìn)程都會(huì)進(jìn)到copyonwrite里,kalloc+kfree者春,這樣復(fù)制了8頁(yè)晰筛,反而消耗更多了怜瞒。

簡(jiǎn)單的加一個(gè)讀取引用計(jì)數(shù),判斷是否為1脾歇,如果為1踢京,就不alloc, 直接設(shè)置WRITE權(quán)限的做法,會(huì)造成測(cè)試不通過(guò)。這里我調(diào)了很久够话,又是race condition的問(wèn)題。

問(wèn)題如下:
父進(jìn)程讀取當(dāng)前引用計(jì)數(shù)為2谷徙;決定采取kalloc+kfree的方法册着。然后schedule到子進(jìn)程
子進(jìn)程讀取當(dāng)前引用計(jì)數(shù)為2;決定采取kalloc+kfree的方法钦奋。然后schedule到父進(jìn)程
然后調(diào)用完kfree 2次,其中第二次理疙,還是把原先的那頁(yè)給free了豌熄。所以依然會(huì)有這個(gè)問(wèn)題巷折。

為了解決上述問(wèn)題:
我們需要保證當(dāng)一個(gè)進(jìn)程在讀取引用計(jì)數(shù)并且決定kalloc時(shí)上鞠,另一個(gè)進(jìn)程在讀取引用計(jì)數(shù)時(shí)拿不到鎖萧锉。當(dāng)對(duì)面搞定了,才會(huì)把鎖釋放谦炒。然后這樣就不會(huì)發(fā)生上述情況了还蹲。 當(dāng)然里面還有非常多的細(xì)節(jié)要考慮诵次。比如kalloc 內(nèi)存分配不夠。我的改動(dòng)大概如下:

  1. 新加了一個(gè)kalloc_cow,專門(mén)為copyonwrite函數(shù)調(diào)度。里面會(huì)處理江解,如果引用計(jì)數(shù)是1桨螺,就是不復(fù)制,直接返回之前的地址酿秸,同時(shí)賦值WRITE權(quán)限灭翔。


    1700307924691.png
  2. 修改copyonwrite函數(shù),根據(jù)返回的地址新舊走不同的邏輯允扇。


    1700308041682.png

我們來(lái)看下新方法的效果:


1700308073259.png

可以看到這次的節(jié)約是遞增的缠局,就是我們最壞情況也是很原來(lái)的分配頁(yè)面開(kāi)銷一樣。不會(huì)出現(xiàn)反而要花更多開(kāi)銷的情況考润。

進(jìn)一步優(yōu)化,實(shí)現(xiàn)lazy allocation

1700308253485.png

1700308270098.png

1700308418901.png

1700308464160.png

1700308502844.png

我們?cè)賮?lái)看一下新的效果:

  1. 首先確保功能性測(cè)試都能通過(guò):


    1700308575865.png

    1700308844216.png

simpletest 沒(méi)有大的提升主要是因?yàn)閟brk之后读处,幾乎把每個(gè)頁(yè)都寫(xiě)了糊治。把寫(xiě)的邏輯注釋掉,我們可以再看下效果:


1700308900038.png
1700308917007.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末罚舱,一起剝皮案震驚了整個(gè)濱河市井辜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌管闷,老刑警劉巖粥脚,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異包个,居然都是意外死亡刷允,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)碧囊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)树灶,“玉大人,你說(shuō)我怎么就攤上這事糯而√焱ǎ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵熄驼,是天一觀的道長(zhǎng)像寒。 經(jīng)常有香客問(wèn)我烘豹,道長(zhǎng),這世上最難降的妖魔是什么诺祸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任吴叶,我火速辦了婚禮,結(jié)果婚禮上序臂,老公的妹妹穿的比我還像新娘蚌卤。我一直安慰自己,他們只是感情好奥秆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布逊彭。 她就那樣靜靜地躺著,像睡著了一般构订。 火紅的嫁衣襯著肌膚如雪侮叮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天悼瘾,我揣著相機(jī)與錄音囊榜,去河邊找鬼。 笑死亥宿,一個(gè)胖子當(dāng)著我的面吹牛卸勺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播烫扼,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼曙求,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了映企?” 一聲冷哼從身側(cè)響起悟狱,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎堰氓,沒(méi)想到半個(gè)月后挤渐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡双絮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年浴麻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掷邦。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡白胀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抚岗,到底是詐尸還是另有隱情或杠,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布宣蔚,位于F島的核電站向抢,受9級(jí)特大地震影響认境,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挟鸠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一叉信、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧艘希,春花似錦硼身、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至撒顿,卻和暖如春丑罪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凤壁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工吩屹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拧抖。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓煤搜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親徙鱼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宅楞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 為什么需要虛擬內(nèi)存 shell進(jìn)程由于bug,引發(fā)了隨機(jī)寫(xiě)入某些內(nèi)存地址,這些內(nèi)存地址可能是其他進(jìn)程使用的袱吆,而可能...
    西部小籠包閱讀 229評(píng)論 0 0
  • 本文通過(guò)對(duì)Linux下串口驅(qū)動(dòng)的分析。由最上層的C庫(kù)距淫。到操作系統(tǒng)系統(tǒng)調(diào)用層的封裝绞绒。再到tty子系統(tǒng)的核心。再到一系...
    Leon_Geo閱讀 928評(píng)論 0 3
  • 大學(xué)的時(shí)候榕暇,幫朋友寫(xiě)的操作系統(tǒng)調(diào)研的作業(yè)蓬衡,最近整理過(guò)去的文檔時(shí)候偶然發(fā)現(xiàn),遂作為博客發(fā)出來(lái)彤枢。 從串口驅(qū)動(dòng)到Linu...
    free_will閱讀 7,387評(píng)論 7 59
  • 此篇是學(xué)習(xí)ucore操作系統(tǒng)lab1的實(shí)驗(yàn)報(bào)告狰晚,參考了很多資料和文章,也學(xué)到了不少缴啡。 先看要求: 為了實(shí)現(xiàn)lab1...
    一斗水閱讀 379評(píng)論 0 0
  • 驅(qū)動(dòng) 是操作系統(tǒng)中的一段用于管理特殊設(shè)備的代碼:它配置設(shè)備的硬件壁晒,告訴設(shè)備處理工作,處理產(chǎn)生的中斷业栅,并與可能正在等...
    sarto閱讀 756評(píng)論 0 0