無限遠まで突撃中

ネット日記書きの徒然。

ld-linux.soと共有ライブラリあたりの雑記

2020/09/28 .interp と動的リンカの部分を追記しました

最近ブログ更新していなかったので、最近知ったこと書きます。

あ、 ld-linux.so とか共有ライブラリとかの概略はしません。 この記事読んでいる人はわかっていると思うので。

.interp と動的リンカ

ELF実行形式には .interp と呼ばれるセクションがある場合があります。

これは動的リンカを指定するためのセクションで、動的リンカがあるパスが文字列として埋め込まれています。

カーネルはこの .interp セクションを見て動的リンカ( ld-linux.so など)をロードします。

実行形式のロードとセグメント

ld-linux.so はELFのプログラムヘッダを読んでセグメントをメモリ上に配置していきますが、プログラムヘッダにセクション情報はありません。 動的リンク動的ロードが必要な共有ライブラリの情報は .dynamic セクション にありますが、どうやってこのセクションがあるセグメントを特定しているのでしょうか。

実はセグメントの中には DYNAMIC と呼ばれる種類のセグメントがあり、プログラムヘッダからセグメント種別を見分けることができます。

Hello, World!プログラムをコンパイルした実行ファイルを readelf -l してみてみると次のようになっています。

$ readelf -W -l hello

Elf file type is DYN (Shared object file)
Entry point 0x1040
There are 11 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000000040 0x0000000000000040 0x000268 0x000268 R   0x8
  INTERP         0x0002a8 0x00000000000002a8 0x00000000000002a8 0x00001c 0x00001c R   0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x000558 0x000558 R   0x1000
  LOAD           0x001000 0x0000000000001000 0x0000000000001000 0x0001d5 0x0001d5 R E 0x1000
  LOAD           0x002000 0x0000000000002000 0x0000000000002000 0x000120 0x000120 R   0x1000
  LOAD           0x002de8 0x0000000000003de8 0x0000000000003de8 0x000248 0x000250 RW  0x1000
  DYNAMIC        0x002df8 0x0000000000003df8 0x0000000000003df8 0x0001e0 0x0001e0 RW  0x8
  NOTE           0x0002c4 0x00000000000002c4 0x00000000000002c4 0x000044 0x000044 R   0x4
  GNU_EH_FRAME   0x002014 0x0000000000002014 0x0000000000002014 0x000034 0x000034 R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x002de8 0x0000000000003de8 0x0000000000003de8 0x000218 0x000218 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 
   03     .init .plt .text .fini 
   04     .rodata .eh_frame_hdr .eh_frame 
   05     .init_array .fini_array .dynamic .got .got.plt .data .bss 
   06     .dynamic 
   07     .note.gnu.build-id .note.ABI-tag 
   08     .eh_frame_hdr 
   09     
   10     .init_array .fini_array .dynamic .got

確かに DYNAMIC タイプのセグメントがあります。

セクションとセグメントのマップ情報を見ても、 DYNAMIC セグメントには .dynamic セクションが入っています。 DYNAIMC セグメントを読みに行けば .dynamic セグメントが解析できるわけですね。

.dynamic セクション

.dynamic セクションのエントリは次のようになっています。

typedef struct
{
    Elf64_Sxword d_tag;
    union {
        Elf64_Xword d_val;
        Elf64_Addr d_ptr;
    } d_un;
} Elf64_Dyn;

extern Elf64_Dyn _DYNAMIC[];

d_tag で情報の種類を、 d_un で情報本体を表します。

d_tagDT_NEEDED タイプのエントリには必要な共有ライブラリ情報が格納されており、 ld-linux.so はこれを読んで共有ライブラリをロードするわけです。 ちなみに DT_NEEDED タイプのエントリの d_un には文字列テーブルのオフセットが記されています。 このオフセットからテーブルを読むとnull終端された文字列が手に入ります。

文字列はどこ

文字列テーブルと言いましたが、これはどこにあるのでしょうか。

これも .dynamic セクションにあって、 DT_STRTAB タイプのエントリを読むと d_un の値が文字列テーブルのアドレスとなっています。

これで無事に共有ライブラリの名前を手に入れた ld-linux.soファイルシステムから共有ライブラリを読み込むわけですね。

ダイレクトマーケティングコーナー

これいいっすよ。

追記は

またします。

酒に酔った

プレモル500ml缶で酔うお手軽ボディは大変よいものでございます、突撃隊です。

疲れからか酒をコンビニで買ってぐいっといっていい気分になったからこんな記事を書いているわけでございます。

将来

どうするんでしょう。 OSいじって就職できるとあんまり思えないし、なんかWebのやつやんなきゃいけないかなあ。

できれば低レイヤ就職したいんですがどうしたもんですかね。

学業

GPAは破滅しているのでどうするか。

院進は今の所したいなあと思っているのですが、推薦はもらえなさそうなので、普通に大学院入試を受ける必要がありそうです。

そもそも親氏は許してくれますかね? 二浪もしているので留年したら割と行かせてもらえなそう。

自由研究

自作OSは最近調子よくて、なんかバリバリ実装しています。

コードレビューしてもらいながら開発しているとたいへん効率がよく、効率がいいです。 fork()とか実装しちゃってるもんね。

ファイルシステム実装までは行きたいですね。

まとめ

以上がお気持ちです。

自作OSのmallocを大きなバイト数でアライメントできるようにしたい

こんにちは、突撃隊です。

コロナで皆さん大変ですが、自分は作業がはかどっています。 なんとも皮肉なもんです。

自作OSのmallocで大きなアライメントを取りたい

タイトルのとおりです。 4KBとか1MBとかのアライメントを取りたいねという話です。

自分の自作OS「minOSv2」では動的メモリ確保にK&R mallocを実装しています。

github.com

mallocの基本構造は らじうむ覚書さんの K&R Malloc解説 を読むと良いです。

以下の文章ではK&R mallocの基本概念はわかっているとします。

mallocのヘッダサイズと16バイトアライメント

minOSv2のmallocヘッダは以下のように定義されています。

minOSv2/mm.h at feature-ext2 · Totsugekitai/minOSv2 · GitHub

struct malloc_header {
    struct malloc_header *next;
    uint64_t size; // This param's unit is sizeof(struct malloc_header).
};

ポインタ型と uint64_t 型の構造体なので、構造体のサイズは16バイトです。

K&R mallocはヘッダのサイズを1単位として扱うので、つまりmallocの単位としては16バイトずつの確保になります。

リンカスクリプトでヒープ領域をアライメントしてあげると、mallocで確保された領域は自動的に16バイトアライメントされていることになります。

16バイトより大きなアライメントを取るには

リンカスクリプトで調整してあげることによって16バイトアライメントが自動的になされることがわかりました。

しかし16バイトより大きいアライメントを取りたいときがあります。

そう、 ハードウェアの操作 をするときです。

AHCIの操作

この前までAHCIデバイスドライバを書くために奮闘していました。

totsugekitai.hatenablog.com

totsugekitai.hatenablog.com

AHCIデバイスドライバを書く際には128Bアライメントや4KBアライメントでメモリを確保する必要があります。

mallocくんは16バイトアライメントは守ってくれますが、それより大きなアライメントは対応していませんので、なんとかしてやる必要があります。

大きなアライメントに対応したメモリ確保をする関数を作る

まず、アライメントを調整するために何バイトmallocする必要があるでしょうか。

size バイトを al バイトでアライメントするときを考えると、 size + al バイト確保してやり、この確保したメモリ上で開始アドレスをいじってやればアライメントを守ったメモリが確保できそうです。

つまりこうすれば解決ですね。

void *not_aligned_allocate = malloc(size + al);
void *aligned_allocate = align(not_aligned_allocate, al);

void *align(void *addr, int align)
{
    uint64_t addr_num = (uint64_t)addr;
    return (void *)(addr_num + (align - (addr_num % align)));
}

freeができない

しかしこのままでは問題があります、freeができないのです。

というのもK&R Mallocのfreeは、引数で受け取ったアドレスの一個手前にmallocヘッダがあると仮定した処理をしています。

今私たちはmallocしたときに返ってきたアドレスを何バイトか後ろにずらして使っていますので、こうすることはできないのです。

free(aligned_allocate);

何バイトずらしたか情報をもたせたい

何らかの手を考える必要があります。

私たちが知りたいのは何バイトずらしたかという情報です。 この情報さえあれば、大きくアライメントされていても元に戻すことができます。

ここで考えてみると、mallocは16バイトごとにメモリを確保し、そして16バイトより大きくアライメントされているときには、 最低でも16バイトは元の確保したメモリの先頭アドレスからずらしているはずです(ここでは2の累乗バイトアライメントを考えています)。

ということはアライメントした後のアドレスからアドレスが小さい方向に16バイトは何にも使われないことになります。

そういうわけでここに情報を書き込んじゃいましょう。

こちらがソースコードになります。

void *kmalloc_alignas(int size, int align_size)
{
    void *tmp = kmalloc(size + align_size);
    if (is_aligned(tmp, align_size)) {
        return tmp;
    } else {
        void *true_ptr = align(tmp, align_size);
        uint64_t diff = (uint64_t)true_ptr - (uint64_t)tmp;
        putsn_serial("malloc align diff: ", diff);
        ((uint64_t *)true_ptr)[-1] = diff;
        strcpy(&((char *)true_ptr)[-16], "ALIGNED");
        return true_ptr;
    }
}

void kfree_aligned(void *ptr, int align_size)
{
    if ((unsigned long)align_size <= sizeof(struct malloc_header)) {
        kfree(ptr);
    } else {
        if (strcmp(&((char *)ptr)[-16], "ALIGNED") == 0) {
            uint64_t diff = ((uint64_t *)ptr)[-1];
            void *true_ptr = (void *)((char *)ptr - diff);
            putsn_serial("kfree addr diff: ", diff);
            putsp_serial("kfree true free address point: ", true_ptr);
            kfree(true_ptr);
        } else {
            kfree(ptr);
        }
    }
}

kmalloc_alignasとkfree_alignedという関数を作りました。

まずkmalloc_alignasでは、kmallocで確保したメモリ領域のアライメントがあっているかチェックし、あっていたらそのままリターンします。 あっていなかったら、アライメントした後のアドレスの前8バイトにアライメントのために移動させた差の値を書き込みます。

またアライメントした後のアドレスの前16バイトから前9バイトにかけて "ALIGNED" という識別子を置きました。

kfree_alignedでは、まずアライメントサイズを確認し、16バイト以下だったらずらされていないので普通にfreeします。

16バイトより大きい場合、アライメントのためにずらされているかどうかを "ALIGNED" の識別子があるはずの部分を見て判断します。 識別子があったらずらされていると判断し、アライメントのためにどれだけずらされたかを差の値があるはずの部分を読んで計算し直し、mallocヘッダが読み込まれるようにアドレスを調整してからfreeします。

いかがでしたか?

アライメントはたいへんですね、そういえばアライメントの話だとこんな記事もあります。

uchan.hateblo.jp

いや〜アライメントはたいへんですね、気をつけましょう。

AHCI奮闘記 - Read/Write編

AHCIの読み出し書き込みに成功した

こんにちは、突撃隊です。 AHCIを用いたSATAディスクへの読み書き(QEMU上ですが)が無事成功したので、メモを残しておこうかと思います。

実機確認はまだできていませんので話半分にお願いします。 実機確認できました!

以下の記事 totsugekitai.hatenablog.com

で解説されている初期化を行ったものとして以降は解説していきます。

また、一挙一足説明していると時間が足りないので、そこらへん足りない部分は自分のリポジトリを見て下さい。

github.com

仕様書の該当部分を読む

AHCIの仕様書の 5.5 System Software Rules に記述されているので、それのとおりやればできます。

でも落とし穴が多いので解説します。

まずはポートのセッティング

ポートの設定をまずはします。 ここは仕様書の 5.5 には載っておらず、実装の上で必要になったので追加していることをお伝えします。

以下のような処理を行います。

static inline void start_cmd(HBA_PORT *port)
{
    puts_serial("start_cmd start\n");
    port->cmd &= 0xfffffffe;    // PxCMD.ST = 0
    // wait until CR is cleared
    while (port->cmd & 0x8000) { asm volatile("hlt"); }

    // set FRE and ST
    port->cmd |= 0x10;
    port->cmd |= 0x01;
    puts_serial("start_cmd end\n");
}

最初に PxCMD.ST をクリアします。 するとAHCIの状態が遷移して PxCMD.CR が0にクリアされるので、それを待ちます。

それが終わったら、また PxCMD.ST を上げ直しますが、 PxCMD.FRE を上げてから PxCMD.ST を上げます。 順番が大事です。

コマンドをつくる

次にコマンドと呼ばれるものを作ります。 コマンドをAHCIコントローラに送ることによって、AHCIコントローラに命令を送ることができます。

それでは手順を記します。 ATAPIコマンドの部分の記述は省略します。

  1. ポートの空いているスロットを見つける
    • 空いているスロットは、 PxCIPxSACT の両方がクリアされているビットに対応するスロット
    • 空いているスロットのことを以下では pFreeSlot とする
  2. メモリ上にcommand FISを構築する
    • 開始アドレスは PxCLB[CH(pFreeSlot)]:CFIS
    • command typeも指定する
  3. PxCLB[CH(pFreeSlot)] にcommand headerを構築する。条件は以下の通り
    • PRDTL はPRD tableの数を格納する
    • CFL はCFIS領域のサイズを指定する
    • A ビットはクリアする(ATAPIコマンド識別ビット)
    • W ビットはwrite処理ならセット、read処理ならクリアする
    • P ビットはオプショナル(今回はやめておく)
  4. PxCI.CI(pFreeSlot) のビットを立てて、コマンドがアクティブであることをAHCI側に通知する

以上の処理を以下のコードのようにまとめました。 build_command が本体です。

static int find_free_cmdslot(HBA_PORT *port)
{
    uint32_t slots = (port->ci | port->sact);
    for (int i = 0; i < 32; i++) {
        if ((slots & 1) == 0) {
            return i;
        }
        slots >>= 1;
    }
    puts_serial("Cannot find free command list entry\n");
    return -1;
}

#define CMD_TBL_BASE 0x10010000
static inline void build_cmd_table(CMD_PARAMS *params, uint64_t *table_addr)
{
    memset((void *)table_addr, 0, 0x80 + 16 * 65536); // zero clear
    HBA_CMD_TBL *table = (HBA_CMD_TBL *)table_addr;

    // build CFIS
    if (params->fis_type == 0x27) { // if Register H2D
        FIS_REG_H2D *h2dfis = (FIS_REG_H2D *)table_addr;
        h2dfis->fis_type = 0x27;    // H2D FIS type magic number
        h2dfis->c = 1;              // This is command
        // command type is referenced in ATA command set
        h2dfis->command = params->cmd_type;
        // device
        //h2dfis->device = 0xe0;
        h2dfis->device = 1 << 6;
        // LBA
        h2dfis->lba0 = (uint8_t)(((uint64_t)(params->lba) >>  0) & 0xff);
        h2dfis->lba1 = (uint8_t)(((uint64_t)(params->lba) >>  8) & 0xff);
        h2dfis->lba2 = (uint8_t)(((uint64_t)(params->lba) >> 16) & 0xff);
        h2dfis->lba3 = (uint8_t)(((uint64_t)(params->lba) >> 24) & 0xff);
        h2dfis->lba4 = (uint8_t)(((uint64_t)(params->lba) >> 32) & 0xff);
        h2dfis->lba5 = (uint8_t)(((uint64_t)(params->lba) >> 40) & 0xff);
        // block count
        h2dfis->countl = (uint8_t)((params->count >> 0) & 0xff);
        h2dfis->counth = (uint8_t)((params->count >> 8) & 0xff);
    } else {
        puts_serial("fis type error\n");
        return;
    }

    // build PRD Table
    // 8 KB (16 sectors) per PRD Table
    // 1 sectors = 512 KB
    uint16_t count = params->count;
    int prdtl = (int)((params->count - 1) >> 4) + 1;
    uint8_t *buf = (uint8_t *)params->dba;
    int i;
    for (i = 0; i < prdtl - 1; i++) {
        table->prdt_entry[i].dba  = (uint32_t)buf;
        table->prdt_entry[i].dbau = 0;
        table->prdt_entry[i].dbc = 8 * 1024 - 1;
        table->prdt_entry[i].i = 1; // notify interrupt
        buf += 8 * 1024; // 8K bytes
        count -= 16; // 16 sectors
    }

    // Last entry
    table->prdt_entry[i].dba = (uint32_t)buf;
    table->prdt_entry[i].dbc = (count << 9) - 1;
    table->prdt_entry[i].i = 1;
}

static inline void build_cmdheader(HBA_PORT *port, int slot, CMD_PARAMS *params)
{
    HBA_CMD_HEADER *cmd_list = ((HBA_CMD_HEADER *)port->clb + slot);
    memset((void *)cmd_list, 0, 0x400);
    cmd_list->ctba = (uint32_t)CMD_TBL_BASE;
    cmd_list->ctbau = 0;
    cmd_list->prdtl = (uint16_t)(((params->count - 1) >> 4) + 1);
    cmd_list->cfl = params->cfis_len;
    cmd_list->w = params->w;
}

static inline void notify_cmd_is_active(HBA_PORT *port, int slot)
{
    port->ci |= 1 << slot;
}

static inline void build_command(HBA_PORT *port, CMD_PARAMS *params)
{
    int slot = find_free_cmdslot(port);
    // step 1:
    // build a command FIS in system memory at location PxCLB[CH(pFreeSlot)]:CFIS with the command type.
    build_cmd_table(params, (uint64_t *)CMD_TBL_BASE);
    // step 2:
    // build a command header at PxCLB[CH(pFreeSlot)].
    build_cmdheader(port, slot, params);
    // step 3:
    // set PxCI.CI(pFreeSlot) to indicate to the HBA that a command is active.
    notify_cmd_is_active(port, slot);

    puts_serial("build command is over\n");
}

1つの関数で1つのステップを処理しています。

コマンドテーブルの開始アドレスは面倒だったので決め打ちにしました。 ここは malloc などで確保してやったほうがお行儀が良さそうです。 後々直したいと思います。

コマンド特有のパラメータ(例えば W ビットが立っているかどうか)は CMD_PARAMS 型にまとめました。

さて

これでAHCIがよろしくやってくれて、コマンドが実行されます

最後は後処理を忘れずに

最後の終了処理を忘れると痛い目を見るので、しっかり後片付けをしましょう。 では手順を説明します。

  1. IS.IPS を監視して、割り込みが来たことを確認
  2. 対応するポートの PxIS レジスタを確認し、正常な割り込みが来ているか確認
  3. 正常だったら PxIS をクリアする
  4. IS.IPS をクリアする
  5. AHCI側が PxCI をクリアするのを待つ

以上の処理をこんなふうに書いてみました。

static inline void wait_interrupt(HBA_PORT *port)
{
    puts_serial("while waiting interrupt\n");
    while (port->is == 0) {
        asm volatile("hlt");
    }
    puts_serial("interrupt comes\n");
}

static inline void clear_pxis(HBA_PORT *port)
{
    port->is |= port->is;
    puts_serial("while clear PxIS\n");
    while (port->is) {
        asm volatile("hlt");
    }
    puts_serial("clearing PxIS is over\n");
}

static inline void clear_ghc_is(int portno)
{
    HBA_MEM_REG *ghc = (HBA_MEM_REG *)abar;
    ghc->is |= 1 << portno;
    puts_serial("while clear IS.IPS\n");
    while (ghc->is) {
        asm volatile("hlt");
    }
    puts_serial("clearing IS.IPS is finished\n");
}

static inline void wait_pxci_clear(HBA_PORT *port)
{
    puts_serial("wait PxCI\n");
    while (port->ci) {
        asm volatile("hlt");
    }
    puts_serial("wait PxCI end\n");
}

いくつか注意点を述べます。

PxIS をクリアする方法は、 立っているビットに対して1を書き込んでAHCI側がクリアするのを待つ ことです。 これは仕様書にも載っています。 0クリアするために1を書き込むというのは感覚的にむむっ?と来ましたが、まあそういうもんだと思って従います。 IS.IPS のクリア方法も同様ですので承知しておいて下さい。

また、AHCI側が PxCI をクリアするまで待つというのも意外な落とし穴ポイントです。 クリアする前に次のコマンドをアクティブにしてしまうとエラーがおきてしまいますので注意です。

以上をまとめてRead/Write処理を書く

ということでまとめです。 以下のようなコードになりますた。

int ahci_read_test(HBA_PORT *port, int portno, uint64_t start, uint16_t count, uint16_t *buf)
{
    start_cmd(port);
    CMD_PARAMS params;
    params.fis_type = 0x27;
    params.cmd_type = READ_DMA_EXT;
    params.cfis_len = 5;
    params.lba = (uint64_t *)start;
    params.count = count;
    params.dba = (uint64_t *)buf;
    params.w = 0;
    build_command(port, &params);
    wait_interrupt(port);
    clear_pxis(port);
    clear_ghc_is(portno);
    wait_pxci_clear(port);
    return 1;
}

int ahci_write_test(HBA_PORT *port, int portno, uint64_t start, uint16_t count, uint16_t *buf)
{
    start_cmd(port);
    CMD_PARAMS params;
    params.fis_type = 0x27;
    params.cmd_type = WRITE_DMA_EXT;
    params.cfis_len = 5;
    params.lba = (uint64_t *)start;
    params.count = count;
    params.dba = (uint64_t *)buf;
    params.w = 1;
    build_command(port, &params);
    wait_interrupt(port);
    clear_pxis(port);
    clear_ghc_is(portno);
    wait_pxci_clear(port);
    return 1;
}

ahci_read_test では、 portno 番のポートで作業します。 SATAstart 番地から count セクタ分を読み出して、メモリ上の buf 番地へコピーします。

ahci_write_test では、メモリ上の buf 番地からデータを読み出して、SATAstart 番地から count セクタ分だけ書き込みます。

これで読み書きができると思います。 自分はできました。

AHCI奮闘記 - 初期化編

こんにちは、突撃隊です。 最近いじっているAHCIというデバイスについて、な、な、なんと 初期化 が無事終了したので知見を共有したいと思います。

AHCIとはなんぞや

Advanced Host Controller Interface(AHCI)とは、SATAのデバイスコントローラです。 SATAをいい感じに触るためのインターフェースを提供してくれます。

初期化の手順について

しっかり初期化をしないとほとんど使い物にならないかもしれません。 ちゃんと仕様書を読んでしっかり初期化をしましょう。

仕様書の該当箇所

AHCI Specification ver 1.3 では、 10.1.2 System Software Specific Initialization にソフトウェアプログラマがやるべき初期化が書かれています。

初期化手順をまとめた

自分なりにやるべき処理をまとめました。

初期化処理メモ(上から順番)

  • 最初にリセットをしたほうがいいかもしれない
    • リセットはGHC.AE = 1にした後、GHC.HR = 1にしてしばらく待つ
  • PIレジスタを見て実装されているポートを探す
    • 以下の処理はすべて実装されているポートにのみ対して行う
  • ソフトウェア側でPxCMD.STを0にセットしハードウェアがPxCMD.CRを0にするのを待つ
  • 最低500msくらいは待つ
  • (PxCMD.FREが1だった場合、これを0にしてハードウェアがPxCMD.FRを0にするのを待つ(最低500ms))
  • CAP.NCSを読んで対応スロットを探す
  • 各対応スロットにおいて、PxCLBとPxFBのぶんのメモリを確保して0で埋める
  • PxFBのメモリ領域を確保した後、PxCMD.FREを1にする
  • 各対応スロットに対して、PxSERRをクリアする。ビットに'1s'を書き込んでクリアする
  • PxISをクリアした後、IS.IPSをクリアする(順番が重要)
  • PxIEレジスタをenableにする。またGHC.IEも1にセットする

(必要なら追加の初期化をする、その際の注意)

  • バイスが動いている間は、PxCMD.STは1にセットしようとしない
    • 具体的には、PxTFD.STS.BSY=0,PxTFD.STS.DRQ=0,PxSSTS.DET=3hのときである
  • PxTFDレジスタをenableにするために、PxSERR.DIAG.Xは0にクリアされてなければならない

自分の自作OSではどうなっているか

QEMU上で動かしています。

初期化直後は以下のようなフラグの状態です。 多分正常だと思います。

status of Generic Host Control
cap: 0x00000000c0141f05
ghc: 0x0000000080000002
is: 0x0000000000000000
pi: 0x000000000000003f
vs: 0x0000000000010000
ccc_ctl: 0x0000000000000000
ccc_pts: 0x0000000000000000
em_loc: 0x0000000000000000
em_ctl: 0x0000000000000000
cap2: 0x0000000000000000
bohc: 0x0000000000000000

status of HBA Port 0
clb: 0x0000000010000000
clbu: 0x0000000000000000
fb: 0x0000000010008000
fbu: 0x0000000000000000
is: 0x0000000000000000
ie: 0x00000000fdc000ff
cmd: 0x0000000000004016
tfd: 0x0000000000000130
sig: 0x0000000000000101
ssts: 0x0000000000000113
sctl: 0x0000000000000000
serr: 0x0000000000000000
sact: 0x0000000000000000
ci: 0x0000000000000000
sntf: 0x0000000000000000
fbs: 0x0000000000000000

なぐり書きなので

また随時更新します。

Windows updateかけたらrEFIndからArch Linuxが起動できなくなったのをなんとか直した

発端

自分のPCはrEFIndを使ってWindows 10とArch Linuxデュアルブートしているのですが、Windows updateをかけたらEFI領域を見事にぶっ壊されました。 なんやかんやで解決したので今後のためにも自分が行った解決方法を記しておきます。

症状

Windows updateをかけたあとrEFIndから vmlinuz-linux を起動しようとすると

Invalid loader file!
Error: Not Found while loading vmlinuz-linux

* Hit any key to continue *

こんな感じのエラーが。

解決方法

vmlinuz-linux がなんか知らんがおかしいらしく、起動しないので、Arch Linuxのインストールメディアを用いる。 あのArchLinuxInstallBattleに使うやつです。

おなじみコマンド。 sd{X1} とかは自分の環境にあったやつに置き換えてください。

# mount /dev/sd{X1} /mnt
# mount /dev/sd{X2} /mnt/boot
# arch-chroot /mnt

これでとりあえずLinuxに入る。

そして ip コマンドとか iw コマンドとか使ってネットにつないでください。 Free-Wifiがおすすめです( wpa_supplicantCLIむずすぎない?)。

そしたら vmlinuz-linux を更新します。

# pacman -S linux

これで vmlinuz-linux はOK。

これでいいかと思ったらrEFInd君が探してくる vmlinuz から起動しようとしたら、

mount: /new_root: wrong fs type, bad option, bad superblock, on /dev/sda2, missing codepage or helper program, or other error

今度はbtrfs君が死んでいます(いや知らんが…)。

rEFIndの設定を見直して手動でブートディスクを指定します。

/boot/EFI/refind/refind.conf をこんな感じに。

menuentry Arch {
    icon     /EFI/refind/icons/os_arch.png
    volume   "Arch"
    loader   /vmlinuz-linux
    initrd   /initramfs-linux.img
    options  "root=/dev/sd{X1} ro"
    submenuentry "Boot using fallback initramfs" {
        initrd /initramfs-linux-fallback.img
    }
    submenuentry "Boot to terminal" {
        add_options "systemd.unit=multi-user.target"
    }
    #disabled
}

loaderinitrdvmlinuz-linux があるファイルシステムのルートディレクトリからの相対パスにするらしい。 これ豆知識ね。 あと /refind_linux.conf があるとそっちの設定が読み込まれちゃうので refind_linux.conf.old とか適当にリネームしとく。

これでrEFIndの画面からArch Linuxのアイコンのやつを選択して起動!

起動したわw

まとめ

(大事なレポートや論文の提出間近にWindows updateは)してはいけない(戒め)。

大きく振り返って

この記事は coins Advent Calendar 2019 23日目の記事です。 22日目はわかめさんの 天久保周りの飯屋 でした。

早いものでもう2019年が終わろうとしています。 みなさんは有意義な一年にできたでしょうか? 自分は今年は色々やった年だったので振り返りを書こうかと思います。

1 - 3月

前半はなんかあんまり良く覚えていません。 色々なことに手を出して撃沈していたような気がします。

春休みに入ると暇になったので自作OSを始めました。

30日でできる! OS自作入門

30日でできる! OS自作入門

この本をやるぞと決めて、一日一章こつこつやりました。

実は購入自体は2018年にしていたのですが、何回かやり始めては挫折するを繰り返していたので、そろそろやりきりたいと思って気合を入れてやりました。 これが4回目の挑戦でした。

途中まで理解したことをブログにまとめていたのですが、大変だったのでやめました。

無事春休み中にやり切ることができました。

その後は64bit環境での自作OSをはじめました。

4 - 5月

セキュリティキャンプ2019全国大会 の募集が始まりました。 自作OSコースがあったので応募してみました。

応募用紙です。

github.com

調子に乗って サイボウズラボユース にも自作OSを作るというテーマで応募しました。

6 - 7月

なんとセキュリティキャンプとラボユースのどちらも選考を通過していました。 セキュリティキャンプ当日に向けて自作OSをガリガリ書く日々が始まりました。 ほぼ毎日3~4時間位はOSの資料を読んだり、コードをウンウンうなりながら書いていたと思います。

8月

セキュリティキャンプに行きました。 詳細な様子は以下です。

totsugekitai.hatenablog.com

大変楽しかったです。

またラボユース合宿にも行きました。温泉宿で3日間の合宿でした。 合宿中はPICの設定で時間を浪費したのが痛かったです。 温泉は大変気持ちよかったです。

9月

まだ夏休みが続いていましたが、体調が優れず、ほとんど活動ができませんでした。 夏休み前半に少し飛ばしすぎたのかもしれません。。。

10 - 12月

体調が戻ったので活動を再開しました。 自作OSの方もマルチタスクやメモリアロケータなどができ、だんだん形になってきていい感じでした。

まとめ

振り返ってみると、この一年は自作OSのことしかやってないですね。 とくに飲み会とか恋愛とか、大学生らしいことをほとんどやってないのですが、なんか楽しかったのでOKです。 (ま、自作OSはまとまった時間がないと難しいので、それが大学生らしいことということで)

来年度は

何やろうかなと悩んでいますが、候補は以下の2つ。

  • 自作コンテナ(DockerとかLXDとか、そういうやつ)
  • BitVisorを用いたベアメタルプログラミング向けデバッグ環境の作成

自作コンテナはDockerのしくみが知りたくなったからです。 しくみが知りたくなったら自作するのがいいかなと思います。

BitVisorの方は、BitVisor Summitという勉強会にお邪魔したときに、品川先生が誰かやってくれと言っていたのでやりたいなと思いました。 (ベアメタル下のデバッグ環境は自作OSをするときにも役立ちますしね!)

あとは本をもうちょっと読みたいですね。