週刊? Linux Kernel Patch Watch 20180413

まえがき

週刊Railsウォッチというrailsを中心とした記事がとても良い感じで、これのカーネル版あったら嬉しなということで自分でやってみました(*´Д`) どれくらいのボリュームでとか、毎週ちゃんと書くのかは全く不明でございます。

我々にはWelcome to LWN.net [LWN.net]Linux Hardware Reviews, Open-Source Benchmarks & Linux Performance - Phoronixがあるし、基本的にこれらを読めば事足りる気はしますが。まあ、小ネタを拾っていこう的な感じです。 読むpatchについてはマージされるかどうかは別として、気になったものとかを取り上げようと思います。

今週のpatch

sl*bでコンストラクタ使う場合にスラブオブジェクトの取得時に__GFP_ZEROフラグ使わない

[PATCH v2 2/2] slab: __GFP_ZERO is incompatible with a constructor — Linux Kernelというpatchのお話です。4/12時点ではマージされてませんが。 sl*bではページを確保してスラブオブジェクトを新規に作る時にコンストラクタを渡して、作成時に処理を行うことができます。スラブの作成でkmem_cache_create()系の関数を使うとコンストラクタを渡せます。

struct kmem_cache *kmem_cache_create(const char *name, size_t size,
            size_t align, slab_flags_t flags,
            void (*ctor)(void *));
struct kmem_cache *kmem_cache_create_usercopy(const char *name,
            size_t size, size_t align, slab_flags_t flags,
            size_t useroffset, size_t usersize,
            void (*ctor)(void *));

slubだとsetup_object()で1オブジェクト毎に指定されたコンストラクタを実行します。

static void setup_object(struct kmem_cache *s, struct page *page,
                void *object)
{
    setup_object_debug(s, page, object);
    kasan_init_slab_obj(s, object);
    if (unlikely(s->ctor)) {
        kasan_unpoison_object_data(s, object);
        s->ctor(object);
        kasan_poison_object_data(s, object);
    }
}

そして、kmem_cache_alloc()でスラブオブジェクトを取得する時にgfpフラグを渡して、メモリの確保方法を指定できます。

void *kmem_cache_alloc(struct kmem_cache *, gfp_t flags) __assume_slab_alignment __malloc;

ここで、flagsにGFP_ZEROを渡した場合ですが、オブジェクトを確保してそのオブジェクトをmemset()で0クリアするのがslubでの処理です。そうすると、コンストラクタで何かしらの処理をオブジェクトにしていてもそれが0で上書きされてしまうので、その場合の対応が今回のpatchでした。 簡単なテストコードを書いて試してみましょう。コードはこんな感じです。kmem_cache_alloc()にGFP_ZEROを渡さない場合と渡す場合の2関数を作って呼びます。

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/gfp.h>
#include <linux/string.h>

MODULE_DESCRIPTION("slab constructor test");
MODULE_AUTHOR("masami256");
MODULE_LICENSE("GPL");

#define SCT_SLAB_NAME "sct"

static struct kmem_cache *sct_cache;

struct sct_test_struct {
    int n;
    char s[16];
};

static void sct_constructor(void *data)
{
    struct sct_test_struct *p = (struct sct_test_struct *) data;
    memset(p->s, 0x0, sizeof(p->s));

    p->n = 0xffff;
    strcpy(p->s, "hello");
}

static void sct_test(void)
{
    struct sct_test_struct *p = kmem_cache_alloc(sct_cache, GFP_KERNEL);

    pr_info("%s: %d, %s\n", __func__, p->n, p->s);

    kmem_cache_free(sct_cache, p);
}

static void sct_test_with_gfp_zero(void)
{
    struct sct_test_struct *p = kmem_cache_alloc(sct_cache, __GFP_ZERO);

    pr_info("%s: %d, %s\n", __func__, p->n, p->s);

    kmem_cache_free(sct_cache, p);
}

static int slab_ctor_test_init(void)
{
    slab_flags_t flags = SLAB_PANIC;

    pr_info("%s start\n", __func__);

    sct_cache = kmem_cache_create(SCT_SLAB_NAME,
                      sizeof(struct sct_test_struct),
                      __alignof__(struct sct_test_struct),
                      flags,
                      &sct_constructor);
    if (!sct_cache) {
        pr_info("failed to create slab cache\n");
        return -ENOMEM;
    }

    sct_test();
    sct_test_with_gfp_zero();

    return 0;
}

static void slab_ctor_test_cleanup(void)
{
    if (sct_cache)
        kmem_cache_destroy(sct_cache);

    pr_info("%s bye\n", __func__);
}

module_init(slab_ctor_test_init);
module_exit(slab_ctor_test_cleanup);

実行結果はこうなります。__GFP_ZEROを付けた方はコンストラクタ設定した値が0で上書きされてますね/(^o^)\

masami@kerntest:~/slab_ctor_test$ dmesg
[  225.077109] slab_ctor_test: slab_ctor_test_init start
[  225.077156] slab_ctor_test: sct_test: 65535, hello
[  225.077157] slab_ctor_test: sct_test_with_gfp_zero: 0, 

(´-`).。oO(言われてみればその通りで、コンストラクタと__GFP_ZEROが共存するような使い方はあまり意味がないんだけどシステム的にはノーチェックだったんですね。ちなみに、これはf2fsの方で見つかったバグが発端になってるようです

アドレスを定義するときはunsigned longを使おう

[PATCH] x86/mm: vmemmap and vmalloc base addressess are usngined longs — Linux Kernelのpatchです。

こんな感じでULを付けましょうということです。

-#define __VMEMMAP_BASE_L4   0xffffea0000000000
-#define __VMEMMAP_BASE_L5  0xffd4000000000000
+#define __VMEMMAP_BASE_L4  0xffffea0000000000UL
+#define __VMEMMAP_BASE_L5  0xffd4000000000000UL

(´-`).。oO(忘れずにULをつけましょう

sizeof(void)とgccのワーニング

www.spinics.netのpatchです。gccのオプションで-Wpointer-arithを付けると c sizeof(void) を使ってる時に警告がでます。この警告-Wallではでません。sparseがこの警告を大量に出してくるので警告のon/off切り替えをできるようにしたようです。

#include <stdio.h>

int main(int argc, char **argv)
{
        sizeof(void);
        return 0;
}
masami@saga:~$ gcc sizeof_void.c -Wpointer-arith
sizeof_void.c: In function ‘main’:
sizeof_void.c:5:9: warning: invalid application of ‘sizeof’ to a void type [-Wpointer-arith]
  sizeof(void);
         ^~~~

(´-`).。oO(sizeof(void)は1です

あとがき

来週も書けるかな?

gccのインラインアセンブラ内からcのラベルにgotoでジャンプ

最近のgccインラインアセンブラ内からcのgotoラベルに飛べる仕組みがあるのでそれのメモです。

ドキュメントはUsing the GNU Compiler Collection (GCC): Extended AsmUsing the GNU Compiler Collection (GCC): Extended Asmです。たしかLKMLに送られてたpatchにもこれを使ったコードがあった気がします。

それはさておき、gotoを使う場合の書式は以下のようになっていて、asm gotoという形式です。

asm [volatile] goto ( AssemblerTemplate 
                      : 
                      : InputOperands
                      : Clobbers
                      : GotoLabels)

ドキュメントには-ansiオプションを付けてコンパイルする場合はasmではなくてasmを使うようにと書かれています。また、出力のオペランドは指定できないようです。そのため、計算の途中経過を保持したいときとかはClobbersのところでメモリに書き出すようにするみたいです。

以下が簡単なサンプルです。argcの数が3かどうか調べて3だったらsameラベルにジャンプしてます。

#include <stdio.h>

int main(int argc, char **argv)
{
    __asm__ goto("cmpl $0x3, %[argc]\n\t"
             "je %l[same]\n\t"
             :
             : [argc] "r"(argc)
             :
             :same);

    printf("argc is %d\n", argc);

    return 0;
same:
    printf("argc == 3\n");
    return 0;
}

機械語になるとこんな感じです。

0000000000400527 <main>:                                                                                                                                                                                           
  400527:       55                      push   %rbp                                                                                                                                                                
  400528:       48 89 e5                mov    %rsp,%rbp                                                                                                                                                           
  40052b:       48 83 ec 10             sub    $0x10,%rsp                                                                                                                                                          
  40052f:       89 7d fc                mov    %edi,-0x4(%rbp)                                                                                                                                                     
  400532:       48 89 75 f0             mov    %rsi,-0x10(%rbp)                                                                                                                                                    
  400536:       8b 45 fc                mov    -0x4(%rbp),%eax                                                                                                                                                     
  400539:       83 f8 03                cmp    $0x3,%eax                                                                                                                                                           
  40053c:       74 1b                   je     400559 <main+0x32>                                                                                                                                                  
  40053e:       8b 45 fc                mov    -0x4(%rbp),%eax                                                                                                                                                     
  400541:       89 c6                   mov    %eax,%esi                                                                                                                                                           
  400543:       bf 00 06 40 00          mov    $0x400600,%edi                                                                                                                                                      
  400548:       b8 00 00 00 00          mov    $0x0,%eax                                                                                                                                                           
  40054d:       e8 ee fe ff ff          callq  400440 <printf@plt>                                                                                                                                                 
  400552:       b8 00 00 00 00          mov    $0x0,%eax                                                                                                                                                           
  400557:       eb 0f                   jmp    400568 <main+0x41>                                                                                                                                                  
  400559:       bf 0c 06 40 00          mov    $0x40060c,%edi                                                                                                                                                      
  40055e:       e8 cd fe ff ff          callq  400430 <puts@plt>                                                                                                                                                   
  400563:       b8 00 00 00 00          mov    $0x0,%eax
  400568:       c9                      leaveq
  400569:       c3                      retq
  40056a:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

低レベルプログラミング

低レベルプログラミング

vdsoの領域を読んでファイルに保存

ふとvdsoで提供されている関数の一覧が見たくなったので。

dump vdso

masami@saga:~$ gcc read_vdso.c -o read_vdso                                                                                                                                                                        
masami@saga:~$ ./read_vdso                                                                                                                                                                                         
[*]read 2 pages                                     
[*]vdso start address is 0x7ffd532fb000             
[*]write data                                       
[*]Done.                                            
masami@saga:~$ file vdso_dump.so                                                                                                                                                                                   
vdso_dump.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=dbcfbfba9068a599aa0b0964ac2ab58244ce7cb4, stripped                                                         
masami@saga:~$ readelf -s vdso_dump.so                                                                                                                                                                             

Symbol table '.dynsym' contains 10 entries:         
   Num:    Value          Size Type    Bind   Vis      Ndx Name                                          
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND                                               
     1: 0000000000000a20   773 FUNC    WEAK   DEFAULT   12 clock_gettime@@LINUX_2.6                      
     2: 0000000000000d30   449 FUNC    GLOBAL DEFAULT   12 __vdso_gettimeofday@@LINUX_2.6                
     3: 0000000000000d30   449 FUNC    WEAK   DEFAULT   12 gettimeofday@@LINUX_2.6                       
     4: 0000000000000f00    21 FUNC    GLOBAL DEFAULT   12 __vdso_time@@LINUX_2.6                        
     5: 0000000000000f00    21 FUNC    WEAK   DEFAULT   12 time@@LINUX_2.6                               
     6: 0000000000000a20   773 FUNC    GLOBAL DEFAULT   12 __vdso_clock_gettime@@LINUX_2.6               
     7: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  ABS LINUX_2.6                                     
     8: 0000000000000f20    42 FUNC    GLOBAL DEFAULT   12 __vdso_getcpu@@LINUX_2.6                      
     9: 0000000000000f20    42 FUNC    WEAK   DEFAULT   12 getcpu@@LINUX_2.6   

vdsoの開始アドレスはgetauxval(3)にAT_SYSINFO_EHDRを渡すと取得できる。

xv6のメモリ管理周りのコードリーディング

xv6のページング周りをちょっと見てたので記録をφ(..)メモメモ

参照したドキュメントはbook-rev10.pdfです。

book-rev10.pdfのP21、 Figure 1-2にxv6のメモリレイアウトがあります。

f:id:masami256:20180305233136p:plain

仮想アドレスの0から0x80000000がユーザー空間で、0x80000000〜0xFFFFFFFFがカーネル空間ですね。カーネル空間のうち先頭の0x100000バイトはBIOSの領域となってます。

カーネルアドレス空間vm.cにあるkmap構造体で管理してます。

static struct kmap {
  void *virt;
  uint phys_start;
  uint phys_end;
  int perm;
} kmap[] = {
 { (void*)KERNBASE, 0,             EXTMEM,    PTE_W}, // I/O space
 { (void*)KERNLINK, V2P(KERNLINK), V2P(data), 0},     // kern text+rodata
 { (void*)data,     V2P(data),     PHYSTOP,   PTE_W}, // kern data+memory
 { (void*)DEVSPACE, DEVSPACE,      0,         PTE_W}, // more devices
};

KERNBASEやKERNLINKなどの定数はmemorylayout.hにて定義されています。

#define EXTMEM  0x100000            // Start of extended memory
#define PHYSTOP 0xE000000           // Top physical memory
#define DEVSPACE 0xFE000000         // Other devices are at high addresses

// Key addresses for address space layout (see kmap in vm.c for layout)
#define KERNBASE 0x80000000         // First kernel virtual address
#define KERNLINK (KERNBASE+EXTMEM)  // Address where kernel is linked

V2Pはこんなマクロです。

#define V2P(a) (((uint) (a)) - KERNBASE)

ここでkmapのコメントにあるようなメモリレイアウトにしてるわけですね。

//   0..KERNBASE: user memory (text+data+stack+heap), mapped to
//                phys memory allocated by the kernel
//   KERNBASE..KERNBASE+EXTMEM: mapped to 0..EXTMEM (for I/O space)
//   KERNBASE+EXTMEM..data: mapped to EXTMEM..V2P(data)
//                for the kernel's instructions and r/o data
//   data..KERNBASE+PHYSTOP: mapped to V2P(data)..PHYSTOP,
//                                  rw data + free physical memory
//   0xfe000000..0: mapped direct (devices such as ioapic)

このkmap構造体を使ってマッピングを行っているのがsetupkvm()です。setupkvm()の戻り値の型はpde_t *で、ようはページグローバルディレクトリです。これのアドレスがcr3レジスタに設定されます。

setupkvm()を呼ぶのは4箇所有ります。

exec()ではexecシステムコールで既存のプロセスのアドレス空間を新しいプロセスのアドレス空間に変える時に呼んでいます。userinit()は一番最初のユーザープロセスを作る時に呼んでいます。 kvmalloc()ではカーネルのスケジューラー用に作っているようです。最後のcopyuvm()はfork()の実行時に親プロセスのページテーブルをコピーする時に呼んでいます。

実際にマッピングを行っているのはmappages()です。mappages()は引数としてページグローバルディレクトリの先頭アドレス、マッピングしたい仮想アドレス、そのアドレスのサイズ、仮想アドレスにマッピングする物理アドレス、ページに設定するパーミッションを受け取ります。1番目のページグローバルディレクトリは呼び出し側がkalloc()でメモリを確保しています。kalloc()はkalloc.cに実装があって、空きページを1ページ確保して返す関数です。

mappages()を呼び出している箇所は4箇所です。

setupkvm()は先程も出てきたとおりで、kmap構造体のデータを実際にマッピングしてます。inituvm()はuserinit()の処理中に呼ばれます。_binary_initcode_startなどはカーネルをビルドした後にできるkernel.symというファイルで確認できます。allocuvm()はユーザー空間の大きさを大きくする時に呼んでいます。copyuvm()はfork()の実行時に呼ばれます。

ページフォルトですがxv6ではデマンドページングはやっていないのでエラーとして処理してます。処理しているのはtrap.cにあるtrap()です。カーネルアドレス空間ページフォルトが発生した場合はpanic、ユーザー空間の場合はプロセスを終了させるようです。

default:
    if(myproc() == 0 || (tf->cs&3) == 0){
      // In kernel, it must be our mistake.
      cprintf("unexpected trap %d from cpu %d eip %x (cr2=0x%x)\n",
              tf->trapno, cpuid(), tf->eip, rcr2());
      panic("trap");
    }
    // In user space, assume process misbehaved.
    cprintf("pid %d %s: trap %d err %d on cpu %d "
            "eip 0x%x addr 0x%x--kill proc\n",
            myproc()->pid, myproc()->name, tf->trapno,
            tf->err, cpuid(), tf->eip, rcr2());
    myproc()->killed = 1;

はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみ (Software Design plus)

はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみ (Software Design plus)

ページングでメモリを割り当てる処理の動きを確認できるものを作ってみた

Linuxのしくみの5章でページングの説明がありますね。なんとなく、この辺の挙動をエミュレートする感じのものでも作ってみようかなと思ったりしたので。

リポジトリはこちらです。

github.com

ページングのコードを実際に書くなら自作OSを書くのが良いと思いますが、ページの割り当てくらいならユーザーランドのプログラムでもそれっぽい感じにはなるかなということで^^;

動かし方はこんな感じで、4MiBのメモリがあって、4プロセスが5回ずつページの割り当てを行った感じが下のコマンドです。

masami@saga:~/codes/how_paging_works (master=)$ ./pg -p 4 -l 5 -m 4 | jq .

出力はjson形式になるようにしたので、pipeでjqコマンドに渡せば綺麗に見れます。

{
  "physical memory": {
    "size": "4MiB",
    "range": {
      "start": "0x7fd86ae2e000",
      "end": "0x7fd86b22e000"
    },
    "page flames": 1024
  },
  "page data": [
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae2e000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae2f000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae30000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae31000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae32000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae33000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae34000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae35000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae36000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae37000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae38000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae39000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae3a000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae3b000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae3c000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae3d000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae3e000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae3f000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae40000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae41000"
    }
  ]
}

プロセスのアドレス空間(仮想アドレス)は0x100000000000からスタートしてます。物理アドレスの方はposix_memalign(3)で取得したアドレスで、ASLRが有効な環境というのもあってどんな値かは基本的に予測できません。その代わりページサイズでアライメントを調整してあります。

このプログラムがやってるのは、こんな感じです。

  1. 最初に指定されたサイズのメモリを確保
  2. ページフレームを管理する構造体の初期化
  3. -pオプションで指定された数のスレッドを準備(プロセスの代わりにスレッドを使ってます)
  4. スレッドは-lオプションで指定された回数のループでページの割り当てを行う
  5. 全部のスレッドが終了したらjson形式のデータを出力

ページグローバルディレクトリのpgdはスレッドの開始時にそのスレッド用に作ってます。pmd、pud、pteはdo_page_fault()の中で作ってます。なんでdo_page_fault()って名前かって言うと、ページフォルトが起きた時に呼ばれる関数がこれなので(ここから更に関数の呼び出しはありますが)、それっぽい名前にしました。 他にもpgdやpte用のメモリ確保はpgd_alloc()や、pte_alloc()でLinuxを同じような名前にしてます。

これ自体は単にメモリを確保していって最後に表示するだけですが、適当に弄るともうちょっと遊べるんじゃないかと思いますm( )m

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識 読みました

著者の武内さんより「[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識」を献本いただきました。ありがとうございますm( )m

f:id:masami256:20180218160317j:plain:w250

武内さんはQiitaなどでもわかりやすいカーネルの解説記事を書かれているので、記事を読んだことが有る方も多いかと思います。そして、今回「試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識」というOS・ハードウェアのしくみを手を動かして学ぶコンセプトの本を書かれました。

gihyo.jp

本書の「実験と図解で学ぶOSとハードウェアの基礎知識」というコンセプトはOSの解説本にしては珍しいと思います。しかし、何かを学ぶ上で実際に手を動かしたほうが勉強になるということを考えるとこのコンセプトは良いですよね。 また、図解の部分も丁寧に解説されています。第5章の仮想記憶、ページングの図解などはここまで細く説明されている文献はそうそうないと思います。もちろん5章だけでなく他の章も図が豊富に使われていて理解がはかどります。

章立ては1〜8章まであって、1章はコンピュータシステムの概要説明となり、以降の章に入る前の基礎知識を説明しています。2〜7章は基本的には他の章への依存は少ないので興味のあるところから読むというのも良いと思います。例えば、2章のプロセス生成では仮想記憶に関する知識も合ったほうがよく、仮想記憶の詳細は5章で解説しますという形になっています。 しかし、2章では仮想記憶の詳細がわからないくて理解できるようにプロセス管理についてを説明されているので、本を読み進めるために必要な前提知識といったハードルは低くなっています。

また、解説は本質的な内容の理解というところに注力していると思います。たとえば、Linuxx86_64ならユーザモードとカーネルモードの2種類のモードがあって、ユーザモードは実行可能な命令が制限されるというのがありますよね。これもcpuの機能だとx86Ring Protection機能が出てきたししますが、そういった詳細な部分には踏み込まずにモード分けることの意味について説明されているといった感じです。細かい内容はともかく一番大事なところの知識を得ることができます。詳細を知りたくなったら、あとがきに書かれている書籍にチャレンジしましょう!

自分はLinuxカーネルもくもく会というネタをLinuxカーネルに絞ったもくもく会を開催しているのですが、そうするとカーネルに興味があるけどどこから手を付ければ良いのか???というような悩みを聞くことがよくあります。カーネルに興味があって、その中でも明確にXXXという機能に興味があるという場合は良いですが、そうでない場合も結構ありますよね。そういう人には本当にオススメできる本だと思います。OSカーネルの解説本はLinuxに限らず色々出版されていてどれも良い内容なんですが、最初にどれを読めばいいか?と聞かれると意外と困ったりします。本を読むための前提知識といったハードルは極力低くしつつ、丁寧に解説がされていて、なおかつ手を動かして実際に試せるというところで本書のようなコンセプトの本は入門に最適です。

本書ではLinuxカーネルのコードの説明はしていなくて、普通のアプリケーションプログラムを使って/作ってLinuxのしくみを実験するという方針を取られています。そのため、Linuxカーネルにも手を出したいという場合は、本書とお好きなLinuxカーネルの解説本やカーネルソースコードを用意して本書を読みながらカーネルの実装を調べるという進め方になっていくと思います。といっても、カーネルに慣れていない場合はこのような読み方はしないで、まずは本書をじっくり読み進め、次に本書とカーネルのソースを読み進めるのがオススメな流れです。

OSやハードウェアについて知りたい方は読んで実験してみましょう!

Linux kernelをgdbでリモートデバッグするときはKASLRをoffにするのを忘れずに(´・ω・`)

カーネルgdbでリモートデバッグできるようにするかーとか思ってやってたわけです。

しかし、下記のような感じでちょっとハマってました。

  • ブレークポイントが効かなかったり
  • vmlinuxとvmlinuzの組が合ってるのにシンボル名が出なかったり
  • Cannot access memory at address 0xffffffff8124d730とかで落ちたり

f:id:masami256:20180215221450p:plain

原因はKASLRが有効だったため、実行時にアドレスがランダマイズされていたのが原因ですね\(^o^)/

解決策はKASLRをoffにすれば良いだけです。そのためにカーネルの再ビルドは不要で、カーネルコマンドラインでnokaslrを渡せばOKです。

そうすればこのようにいい感じに f:id:masami256:20180215221904p:plain

以下はめも

remote接続にシリアルコンソールを使う場合

qemuはこんな感じになる。

$ qemu-system-x86_64 -kernel vmlinuz -initrd initramfs.img -m 2048 -append "nokaslr console=tty0 kgdboc=ttyS0,115200 kgdbwait" -chardev pty,id=pty -device isa-serial,chardev=pty 

そうすると以下のようなメッセージが出るので、gdbではこのデバイスに対してアクセスする。

qemu-system-x86_64: -chardev pty,id=pty: char device redirected to /dev/pts/2 (label pty)                

gdbからリモートアクセスするとこんな感じに。

gdb-peda$ target remote /dev/pts/2                  
Remote debugging using /dev/pts/2                   
Warning: not running or target is remote            
kgdb_breakpoint () at kernel/debug/debug_core.c:1073                                                     
1073            wmb(); /* Sync point after breakpoint */  

remote接続にTCPを使う場合

この場合は普通に-sオプションを使えばOK。

$ qemu-system-x86_64 -kernel vmlinuz -initrd initramfs.img -m 2048 -append "nokaslr" -s -S 

gdbからアクセスするときもこのように。

masami@saga:~/tmp/test$ gdb ./vmlinux               
Reading symbols from ./vmlinux...done.              
gdb-peda$ dir usr/src/debug/kernel-4.15.fc27/linux-4.15.2-300.fc27.x86_64/                               
Source directories searched: /home/masami/tmp/test/usr/src/debug/kernel-4.15.fc27/linux-4.15.2-300.fc27.x86_64:$cdir:$cwd                                                                                          
gdb-peda$ target remote :1234                       
Remote debugging using :1234                        
Warning: not running or target is remote            
0x000000000000fff0 in cpu_hw_events ()              
gdb-peda$ b __kmalloc                               
Breakpoint 1 at 0xffffffff8124d730: file mm/slub.c, line 3746.                                           
gdb-peda$ c                                         
Continuing.                                         
Warning: not running or target is remote            

Breakpoint 1, __kmalloc (size=size@entry=0xa8, flags=flags@entry=0x14080c0) at mm/slub.c:3746            
3746    {                                           
gdb-peda$                  

低レベルプログラミング

低レベルプログラミング