systemdでユーザー固有のunitを動かす

systemdは~/.config/systemd/userにserviceファイルを置くことで、そのユーザー用のinit時の処理を動かすことができるんですね。基本的に使い方は通常と同じで、唯一違うのは--userをパラメータとして使用すること。詳細はArch Wikiを見ましょう。

このようにserviceファイルを書いたとして、

masami@kerntest:~$ cat ~/.config/systemd/user/foobar.service 
[Unit]
Description=User's service file

[Service]
Type=oneshot
ExecStart=/usr/bin/touch /tmp/test.txt
RemainAfterExit=yes

[Install]
WantedBy=default.target

このように実行します。

masami@kerntest:~$ systemctl --user enable foobar
Created symlink from /home/masami/.config/systemd/user/default.target.wants/foobar.service to /home/masami/.config/systemd/user/foobar.service.

再起動してstatusを確認するとちゃんと動作したのがわかります。

masami@kerntest:~$ systemctl --user status foobar
● foobar.service - User's service file
   Loaded: loaded (/home/masami/.config/systemd/user/foobar.service; enabled; vendor preset: enabled)
   Active: active (exited) since Thu 2016-05-05 11:32:14 JST; 25s ago
  Process: 342 ExecStart=/usr/bin/touch /tmp/test.txt (code=exited, status=0/SUCCESS)
 Main PID: 342 (code=exited, status=0/SUCCESS)

May 05 11:32:14 kerntest systemd[336]: Starting User's service file...
May 05 11:32:14 kerntest systemd[336]: Started User's service file.

ちなみに--userを付けないと~/config/systemd/userを見ないので、そんなサービスは無いって怒られます。

masami@kerntest:~$ systemctl  status foobar
● foobar.service
   Loaded: not-found (Reason: No such file or directory)
   Active: inactive (dead)

これは良いですね。

Amazon Web Services クラウドネイティブ・アプリケーション開発技法 一番大切な知識と技術が身につく

Amazon Web Services クラウドネイティブ・アプリケーション開発技法 一番大切な知識と技術が身につく

free(1)のtotalとかusedなどの各項目をカーネルの方から見てみる

free(1)は/proc/meminfoを読みに行くので、Linuxカーネルでどのような変数を見せているのかを調べてみます。カーネルのバージョンは4.5です。あ、swapのほうは今回は見ません。

最初に見るのはfs/proc/meminfo.cで、このファイルが/proc/meminfoに対する操作を定義しています。/proc/meminfoをopenする処理はmeminfo_proc_open()で、実際の処理はmeminfo_proc_show()が行います。

211 static int meminfo_proc_open(struct inode *inode, struct file *file)
212 {
213         return single_open(file, meminfo_proc_show, NULL);
214 }
215 
216 static const struct file_operations meminfo_proc_fops = {
217         .open           = meminfo_proc_open,
218         .read           = seq_read,
219         .llseek         = seq_lseek,
220         .release        = single_release,
221 };
222 
223 static int __init proc_meminfo_init(void)
224 {
225         proc_create("meminfo", 0, NULL, &meminfo_proc_fops);
226         return 0;
227 }

この関数の中で、メモリに関する情報はsi_meminfo()とsi_swapinfo()で取得します。

 41 #define K(x) ((x) << (PAGE_SHIFT - 10))
 42         si_meminfo(&i);
 43         si_swapinfo(&i);

si_meminfo()はこのような関数で、引数で渡されたsysinfo構造体にデータを設定します。

3606 void si_meminfo(struct sysinfo *val)
3607 {
3608         val->totalram = totalram_pages;
3609         val->sharedram = global_page_state(NR_SHMEM);
3610         val->freeram = global_page_state(NR_FREE_PAGES);
3611         val->bufferram = nr_blockdev_pages();
3612         val->totalhigh = totalhigh_pages;
3613         val->freehigh = nr_free_highpages();
3614         val->mem_unit = PAGE_SIZE;
3615 }

変数の内容は名前から大体想像つきますね。totalram_pagesがfree(1)実行時のtotalのところに出る値です。ここでの単位はbyteではなくてページ数です。他のデータも単位はページ数です。これをKiBにするのはmeminfo_proc_show()にあるK()マクロです。x86_64環境ならPAGE_SHIFTは12となっています。なので、2bit左シフトしてページ数からKiBに変更しています。

 41 #define K(x) ((x) << (PAGE_SHIFT - 10))

totalhighとfreehighはCONFIG_HIGHMEMが設定されている場合に意味があるんですが、x86_64用のカーネルならCONFIG_HIGHMEMは定義されていないので、値は0です。

で、free(1)のtotalですが、totalram_pagesはman 5 procに書かれているように、搭載しているRAMの総量ではありません。細かくは追ってないですが、このページ数を設定しているのは主にfree_bootmem_late()free_all_bootmem_core()ですね。 この値を表示するときは下記のようにしています。

 85         seq_printf(m,
 86                 "MemTotal:       %8lu kB\n"
〜略〜
143                 K(i.totalram),

物理メモリの搭載量を知りたい場合は↓でできます。

$ sudo dmidecode -t memory | grep "Size:.*MB" | awk '{ m += $2} END {print m}'

次にusedを見てみようと思いますが、man 1 freeUsed memory (calculated as total - free - buffers - cache)と書かれているので、先にfreeを見ましょう。

まず、表示する時にどの値を見ているか確認して、freeramのまま使っているというのがわかります。

 87                 "MemFree:        %8lu kB\n"
〜略〜
144                 K(i.freeram),

freeramはこのように設定されます。

3610         val->freeram = global_page_state(NR_FREE_PAGES);

procのmanではMemFree(LowFree+HighFree)というように書かれていますが、64bit環境にはLowFreeもHighFreeもないので、単なる空きページ数です。空きページ数の取得にはglobal_page_state()を使っているので、これを見てみます。引数のitemはNR_FREE_PAGESですね。値の読み出しはatomic_long_read()を使っていますが、これはlong型のデータをアトミックに読み込む関数です。なので、読み込んでいるのはvm_stat[NR_FREE_PAGES]です。

120 static inline unsigned long global_page_state(enum zone_stat_item item)
121 {
122         long x = atomic_long_read(&vm_stat[item]);
123 #ifdef CONFIG_SMP
124         if (x < 0)
125                 x = 0;
126 #endif
127         return x;
128 }

というわけで、vm_stat[NR_FREE_PAGES]に値を増やすところを見てみます。これを設定するのは__mod_zone_freepage_state()です。

259 static inline void __mod_zone_freepage_state(struct zone *zone, int nr_pages,
260                                              int migratetype)
261 {
262         __mod_zone_page_state(zone, NR_FREE_PAGES, nr_pages);
263         if (is_migrate_cma(migratetype))
264                 __mod_zone_page_state(zone, NR_FREE_CMA_PAGES, nr_pages);
265 }

実際は、__mod_zone_page_state() -> zone_page_state_add()という流れで下記のように値が設定されます。

113 static inline void zone_page_state_add(long x, struct zone *zone,
114                                  enum zone_stat_item item)
115 {
116         atomic_long_add(x, &zone->vm_stat[item]);
117         atomic_long_add(x, &vm_stat[item]);
118 }

それで、__mod_zone_freepage_state()が呼ばれるのはどんな時かというと、ページを取得するときと解放するときですよね。なので、__free_pages()free_pages()が呼ばれた時にということになります。

取得時はpage_alloc.cにあるbuffered_rmqueue()でpageが取得できている場合に、取得したページ数を減らしています。

2265                 if (!page)
2266                         goto failed;
2267                 __mod_zone_freepage_state(zone, -(1 << order),
2268                                           get_pcppage_migratetype(page));

そんなわけで、ここまでがMemFree/freeの値です。

つぎはbuffersを見ましょう。これはmanでMemory used by kernel buffers (Buffers in /proc/meminfo)と書かれています。 procのmanにはこのように書かれています。

Buffers %lu
                     Relatively temporary storage for raw disk blocks that
                     shouldn't get tremendously large (20MB or so).

Cachedはこうです。

              Cached %lu
                     In-memory cache for files read from the disk (the page
                     cache).  Doesn't include SwapCached.

BuffersとCachedは違いますね。 Buffersの表示は下記のようにやっているのでbufferramそのものです。

146                 K(i.bufferram),

Buffersはnr_blockdev_pages()という関数から取得していますので、これを見てましょう。

3611         val->bufferram = nr_blockdev_pages();

nr_blockdev_pages()はこのようにblock_device構造体にあるnrpagesの合計になっています。

677 long nr_blockdev_pages(void)
678 {
679         struct block_device *bdev;
680         long ret = 0;
681         spin_lock(&bdev_lock);
682         list_for_each_entry(bdev, &all_bdevs, bd_list) {
683                 ret += bdev->bd_inode->i_mapping->nrpages;
684         }
685         spin_unlock(&bdev_lock);
686         return ret;
687 }

block_device構造はメンバ変数が結構あるのでbd_inodeのがあるところだけ抜粋です。米野都の意味は全くわかりません/(^o^)\

452 struct block_device {
453         dev_t                   bd_dev;  /* not a kdev_t - it's a search key */
454         int                     bd_openers;
455         struct inode *          bd_inode;       /* will die */

bd_inodeは見ての通りinodeです。

inode構造体のi_mappingはaddress_space構造体です。

600         struct address_space    *i_mapping;

address_space構造体のnrpagesはこれです。

435         unsigned long           nrpages;        /* number of total pages */

というわけで、Buffersに表示される値はブロックデバイスに関連したinodeのaddress_space構造体に設定されているpage数ということがわかります。そして、Cachedですが、これはsi_meminfo()では設定していません。これを設定するのはmeminfo_proc_show()で計算にはbufferramの値が必要になります。

meminfo_proc_show()でのcachedの計算はこのようにやっています。global_page_state()](http://lxr.free-electrons.com/source/include/linux/vmstat.h?v=4.5#L120)はさっき見たやつですね。

 46         cached = global_page_state(NR_FILE_PAGES) -
 47                         total_swapcache_pages() - i.bufferram;

NR_FILE_PAGESはenumなんですが、コメントがないので定義している場所だと何をする変数なのかわかりません。コミットメッセージを見てみると、もともとはnr_pagecacheという変数だったのが2.6.18-rc1から変更されたようです。

Remove the special implementation for nr_pagecache and make it a zoned counter
named NR_FILE_PAGES.

これもわかりにくいので、使っているところを探します。例えば、__delete_from_page_cache()。これはページキャッシュからページを削除する関数です。__dec_zone_page_state()で値を減らす処理をしています。

227         /* hugetlb pages do not participate in page cache accounting. */
228         if (!PageHuge(page))
229                 __dec_zone_page_state(page, NR_FILE_PAGES);

また、replace_page_cache_page()でページキャッシュにあるページを別のpage構造体に置き換える場合、古いほうのページをキャッシュから削除して、新しいページをセットするときに__inc_zone_page_state()を使っています。

566                 __delete_from_page_cache(old, NULL, memcg);
〜略〜
574                 if (!PageHuge(new))
575                         __inc_zone_page_state(new, NR_FILE_PAGES);

他にも__add_to_page_cache_locked()で使ってます。これはpageをページキャッシュに登録する時に使われるものです。

その他、swapキャッシュへの登録時とswapキャッシュからの削除時にも値の更新があります。swapキャッシュ登録時は__add_to_swap_cache()で。

 96         if (likely(!error)) {
 97                 address_space->nrpages++;
 98                 __inc_zone_page_state(page, NR_FILE_PAGES);
 99                 INC_CACHE_INFO(add_total);
100         }

削除は__delete_from_swap_cache()で。

135 void __delete_from_swap_cache(struct page *page)
136 {
〜略〜
149         address_space->nrpages--;
150         __dec_zone_page_state(page, NR_FILE_PAGES);

そんなわけで、NR_FILE_PAGESはpage cacheとswap cacheに登録されたpageの数でしょう。しかし、procのmanではCachedにはswap cacheは含まないと書かれています。

              Cached %lu
                     In-memory cache for files read from the disk (the page
                     cache).  Doesn't include SwapCached.

もう一度計算式を見ると、total_swapcache_pages()の返り値を減算してますね。これは名前からしてswap cacheに登録されているページ数でしょう。

 46         cached = global_page_state(NR_FILE_PAGES) -
 47                         total_swapcache_pages() - i.bufferram;

total_swapcache_pages()はこのような処理で、address_space構造体の配列「swapper_spaces」のnrpagesの総数です。

 35 struct address_space swapper_spaces[MAX_SWAPFILES] = {
 36         [0 ... MAX_SWAPFILES - 1] = {
 37                 .page_tree      = RADIX_TREE_INIT(GFP_ATOMIC|__GFP_NOWARN),
 38                 .i_mmap_writable = ATOMIC_INIT(0),
 39                 .a_ops          = &swap_aops,
 40         }
 41 };
〜略〜
 52 unsigned long total_swapcache_pages(void)
 53 {
 54         int i;
 55         unsigned long ret = 0;
 56 
 57         for (i = 0; i < MAX_SWAPFILES; i++)
 58                 ret += swapper_spaces[i].nrpages;
 59         return ret;
 60 }

nrpagesを更新しているのはNR_FILE_PAGESを更新するタイミングでやってます。

そして、また計算式に戻ります。global_page_state(NR_FILE_PAGES) がpage cache/swap cacheにあるpage数。total_swapcache_pages()はswap cacheに登録されているpage数。bufferramはblock_device構造体に関連したファイルが使用しているpage数で、page cacheのpage数から色々差っ引いてCachedの値が出ます。

 46         cached = global_page_state(NR_FILE_PAGES) -
 47                         total_swapcache_pages() - i.bufferram;

usedはtotal - free - buffers - cacheということなので、これでuserの値に必要なデータは全部揃ったので表示可能ですね。

free(1)の最後の項目はavailableで、これはmanによると/proc/meminfoのMemAvailableということです。

      available
              Estimation of how much memory is available for starting new
              applications, without swapping. Unlike the data provided by
              the cache or free fields, this field takes into account page
              cache and also that not all reclaimable memory slabs will be
              reclaimed due to items being in use (MemAvailable in
              /proc/meminfo, available on kernels 3.14, emulated on kernels
              2.6.27+, otherwise the same as free)

このavailableもmeminfo_proc_show()で計算します。

 57         /*
 58          * Estimate the amount of memory available for userspace allocations,
 59          * without causing swapping.
 60          */
 61         available = i.freeram - totalreserve_pages;
 62 
 63         /*
 64          * Not all the page cache can be freed, otherwise the system will
 65          * start swapping. Assume at least half of the page cache, or the
 66          * low watermark worth of cache, needs to stay.
 67          */
 68         pagecache = pages[LRU_ACTIVE_FILE] + pages[LRU_INACTIVE_FILE];
 69         pagecache -= min(pagecache / 2, wmark_low);
 70         available += pagecache;
 71 
 72         /*
 73          * Part of the reclaimable slab consists of items that are in use,
 74          * and cannot be freed. Cap this estimate at the low watermark.
 75          */
 76         available += global_page_state(NR_SLAB_RECLAIMABLE) -
 77                      min(global_page_state(NR_SLAB_RECLAIMABLE) / 2, wmark_low);
 78 

最初に、空きページ数のMemFree(i.freeram)から予約済みのページ数を引きます。 次に、使用可能な分のpage cacheのページ数を足します。ここはまず、LRU_ACTIVE_FILEとLRU_INACTIVE_FILEを探します。これは本当にindex用途っぽくて、include/linux/mmzone.hでこのように定義されています。

171 #define LRU_BASE 0
172 #define LRU_ACTIVE 1
173 #define LRU_FILE 2
174 
175 enum lru_list {
176         LRU_INACTIVE_ANON = LRU_BASE,
177         LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
178         LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
179         LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
180         LRU_UNEVICTABLE,
181         NR_LRU_LISTS
182 };

変数のpagesはこのように取得しています。global_page_state()はいつものやつです。NR_LRU_LISTSは5なので、0から4までのindexを使いますね。

 34         unsigned long pages[NR_LRU_LISTS];
〜略〜
 51         for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++)
 52                 pages[lru] = global_page_state(NR_LRU_BASE + lru);

global_page_state()に渡すのは列挙型のzone_stat_itemです。NR_ACTIVE_ANONまでの値を取ってきてますね。

114 enum zone_stat_item {
115         /* First 128 byte cacheline (assuming 64 bit words) */
116         NR_FREE_PAGES,
117         NR_ALLOC_BATCH,
118         NR_LRU_BASE,
119         NR_INACTIVE_ANON = NR_LRU_BASE, /* must match order of LRU_[IN]ACTIVE */
120         NR_ACTIVE_ANON,         /*  "     "     "   "       "         */
121         NR_INACTIVE_FILE,       /*  "     "     "   "       "         */

そして、↓なので、LRU_ACTIVE_FILEは3、LRU_INACTIVE_FILEは2です。よって、zone_stat_itemでいうと、3に該当するのはNR_ACTIVE_ANON、2の方はNR_LRU_BASE/NR_INACTIVE_ANONです。なので、Anonymous mappingされたpage数の合計がpagecacheの初期値でしょうね。

 68         pagecache = pages[LRU_ACTIVE_FILE] + pages[LRU_INACTIVE_FILE];

NR_LRU_BASEは使用箇所からして、pageをlruリストに登録・削除した時に設定されます。これの実装はlinux/mm_inline.hにあります。

 25 static __always_inline void add_page_to_lru_list(struct page *page,
 26                                 struct lruvec *lruvec, enum lru_list lru)
 27 {
 28         int nr_pages = hpage_nr_pages(page);
 29         mem_cgroup_update_lru_size(lruvec, lru, nr_pages);
 30         list_add(&page->lru, &lruvec->lists[lru]);
 31         __mod_zone_page_state(lruvec_zone(lruvec), NR_LRU_BASE + lru, nr_pages);
 32 }
 33 
 34 static __always_inline void del_page_from_lru_list(struct page *page,
 35                                 struct lruvec *lruvec, enum lru_list lru)
 36 {
 37         int nr_pages = hpage_nr_pages(page);
 38         mem_cgroup_update_lru_size(lruvec, lru, -nr_pages);
 39         list_del(&page->lru);
 40         __mod_zone_page_state(lruvec_zone(lruvec), NR_LRU_BASE + lru, -nr_pages);
 41 }

次の行はどちらか小さいほうが選択されて引かれます。

 69         pagecache -= min(pagecache / 2, wmark_low);

wmark_lowはこのように設定されます。実行中のLinuxカーネルが管理している各zoneのwatermarkのindexをWMARK_LOWとしたほうのwatermarkを使います。

 54         for_each_zone(zone)
 55                 wmark_low += zone->watermark[WMARK_LOW];

そして、pagecacheの値をavailableに足します。その次のNR_SLAB_RECLAIMABLEはslabオブジェクトのうち、page回収が可能とセットされているslabのページ数です。

 70         available += pagecache;
 71 
 72         /*
 73          * Part of the reclaimable slab consists of items that are in use,
 74          * and cannot be freed. Cap this estimate at the low watermark.
 75          */
 76         available += global_page_state(NR_SLAB_RECLAIMABLE) -
 77                      min(global_page_state(NR_SLAB_RECLAIMABLE) / 2, wmark_low);

これは、例えば、allocate_slab()でこのように使っています。sはkmem_cache構造体です。この構造体のflags変数にSLAB_RECLAIM_ACCOUNTがセットされていればNR_SLAB_RECLAIMABLEを使う感じです。

1482 
1483         mod_zone_page_state(page_zone(page),
1484                 (s->flags & SLAB_RECLAIM_ACCOUNT) ?
1485                 NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE,
1486                 1 << oo_order(oo));

で、flagsにSLAB_RECLAIM_ACCOUNTを設定するのはkmem_cache_create()の実行時です。例えば、fs/inode.cにあるinode_init()でinode用のslabを作る時に設定しています。

1887         /* inode slab cache */
1888         inode_cachep = kmem_cache_create("inode_cache",
1889                                          sizeof(struct inode),
1890                                          0,
1891                                          (SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|
1892                                          SLAB_MEM_SPREAD|SLAB_ACCOUNT),
1893                                          init_once);
1894 

そして、回収可能なslabのページ数から引き算を行うわけですが、これもどちらか小さいほうが引かれます。

 76         available += global_page_state(NR_SLAB_RECLAIMABLE) -
 77                      min(global_page_state(NR_SLAB_RECLAIMABLE) / 2, wmark_low);

これでavailableの値決定ですね。

動くメカニズムを図解&実験! Linux超入門 (My Linuxシリーズ)

動くメカニズムを図解&実験! Linux超入門 (My Linuxシリーズ)

Linuxカーネルのコマンドラインはブートローダーからどう渡されるのか?

先日参加した自作OSもくもく会で「Linuxカーネルコマンドラインブートローダーからどう渡されるのか?」のような話が聞こえたので、調べようと思い調べてみました。確認はLinux kernel v4.5とsystemd-bootの2016/05/02 23:00 JSTのコードです。 uefiじゃない環境も確認しようかなと思ってgrubのコードをgit cloneはしました。が、うちのメイン環境で使っているのはsystemd-bootだしってことで確認してません。

で、カーネルコマンドラインは↓のようなやつですね。

masami@saga:~/codes$ cat /proc/cmdline
initrd=\initramfs-4.6.0-rc5-ktest+.img root=/dev/sda2 rw crashkernel=256M

コマンドラインカーネルに渡すとしたら、どこかしらのアドレスに置くんだろうというのは想像できますが、ブートローダーの好きな場所に置くとも考えにくいので、何かしらのプロトコルは決まっているはずです。というわけで、カーネルのドキュメントを確認します。確認するのはDocumentation/x86/boot.txtです。 これを見ると、カーネルコマンドラインはヒープの終わりから0xA0000までの間の好きなところに置けると書かれています。

532   Set this field to the linear address of the kernel command line.
533   The kernel command line can be located anywhere between the end of
534   the setup heap and 0xA0000; it does not have to be located in the
535   same 64K segment as the real-mode code itself.

そして、systemd-bootのsrc/boot/efi/linux.cのコードを見ると、0xA0000がありますね。systemd-bootはアドレス0xA0000にカーネルコマンドラインを置くようです。

        if (cmdline) {
                addr = 0xA0000;
                err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData,
                                        EFI_SIZE_TO_PAGES(cmdline_len + 1), &addr);
                if (EFI_ERROR(err))
                        return err;
                CopyMem((VOID *)(UINTN)addr, cmdline, cmdline_len);
                ((CHAR8 *)addr)[cmdline_len] = 0;
                boot_setup->cmd_line_ptr = (UINT32)addr;
        }

そして、if文を抜けた後は、linux_efi_handover()を呼び、この関数からLinuxカーネルに制御が移ります。

#ifdef __x86_64__
typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup);
static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) {
        handover_f handover;

        asm volatile ("cli");
        handover = (handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset);
        handover(image, ST, setup);
}
#else

ここから呼ばれるのはarch/x86/kernel/head_32.Sのstartup_32だと思います。そして、この辺でブートパラメータのコピー処理があります。

127 /*
128  * Copy bootup parameters out of the way.
129  * Note: %esi still has the pointer to the real-mode data.
130  * With the kexec as boot loader, parameter segment might be loaded beyond
131  * kernel image and might not even be addressable by early boot page tables.
132  * (kexec on panic case). Hence copy out the parameters before initializing
133  * page tables.
134  */
135         movl $pa(boot_params),%edi
136         movl $(PARAM_SIZE/4),%ecx
137         cld
138         rep
139         movsl
140         movl pa(boot_params) + NEW_CL_POINTER,%esi
141         andl %esi,%esi
142         jz 1f                   # No command line
143         movl $pa(boot_command_line),%edi
144         movl $(COMMAND_LINE_SIZE/4),%ecx
145         rep
146         movsl

boot_command_lineはinit/main.cにあります。linux/include/init.hでextern宣言しているのでhead_32.Sからもアクセスできます。

122 /* Untouched command line saved by arch-specific code. */
123 char __initdata boot_command_line[COMMAND_LINE_SIZE];

今回はこんなところで( ´ー`)フゥー...

Go言語によるWebアプリケーション開発

Go言語によるWebアプリケーション開発

systemptapでプロセスが所属する各pid名前空間におけるpid番号を取得

systemptapでプロセスが所属する各pid名前空間におけるpid番号を取得する方法のめも。

プロセスのPIDは普通に見れますが、そこで見えているPIDというのは自身が所属しているPID名前空間においてのものです。なので、あるプロセスがunshareなりでpid名前空間を親プロセスと別の場合に、親プロセスの名前空間から見たPIDを知ることはできません。逆に親プロセスからは自身のPID名前空間での子プロセスのPIDを見ることはできますが、子プロセスのPID名前空間におけるPIDはわかりません。ってことで、あるプロセスが所属するPID名前空間でのPIDを全部見ることができるツールsystemtapで書いてみました。

ソースはこちらです。stapのコマンドライン

gist.github.com

まず、dockerコンテナ内でnginxを動かしてみます。

[root@6d02a9c04d45 /]# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 15:13 ?        00:00:00 /bin/bash
root        51     1  0 15:15 ?        00:00:00 nginx: master process nginx
http        52    51  0 15:15 ?        00:00:00 nginx: worker process
root        59     1  0 15:19 ?        00:00:00 ps -ef

このときのホスト側ではプロセスはこう見えています。docker(2768)の下にnginxのプロセスがいますね。

$ pstree -p
systemd(1)-+-agetty(259)
           |-agetty(260)
           |-dbus-daemon(235)
           |-dhcpcd(328)
           |-docker(2768)-+-bash(12494)---nginx(12572)---nginx(12573)
           |              |-{docker}(2769)
           |              |-{docker}(2770)
           |              |-{docker}(2771)
           |              |-{docker}(2772)
           |              |-{docker}(2773)
           |              |-{docker}(2774)
           |              |-{docker}(2775)
           |              |-{docker}(2776)
           |              |-{docker}(2777)
           |              |-{docker}(3019)
           |              |-{docker}(3023)
           |              |-{docker}(3024)
           |              `-{docker}(3025)
           |-sshd(330)-+-sshd(1587)---sshd(1595)---bash(1596)---docker(12432)-+-{docker}(12433)
           |           |                                                      |-{docker}(12434)
           |           |                                                      |-{docker}(12435)
           |           |                                                      |-{docker}(12436)
           |           |                                                      |-{docker}(12437)
           |           |                                                      |-{docker}(12438)
           |           |                                                      |-{docker}(12439)
           |           |                                                      `-{docker}(12532)
           |           |-sshd(1674)---sshd(1676)---bash(1677)---pstree(12949)
           |           `-sshd(12270)---sshd(12272)---bash(12273)
           |-systemd(1589)---(sd-pam)(1590)
           |-systemd-journal(169)
           |-systemd-logind(244)
           |-systemd-timesyn(192)---{sd-resolve}(195)

そして、ホスト側でスクリプトを動かすとこうなって、コンテナ内 -> ホストという感じでpidが表示できました。

masami@nlkb:~$ sudo stap -v -g ./pid.stp 12573
Pass 1: parsed user script and 113 library scripts using 69844virt/36104res/4924shr/31496data kb, in 90usr/10sys/109real ms.
Pass 2: analyzed script: 2 probes, 2 functions, 1 embed, 0 globals using 70636virt/37136res/5180shr/32288data kb, in 10usr/0sys/3real ms.
Pass 3: translated to C into "/tmp/stapfubRbf/stap_6dd962b4096d287515d5450e69c15b96_2149_src.c" using 70636virt/37136res/5180shr/32288data kb, in 0usr/0sys/0real ms.
Pass 4: compiled C into "stap_6dd962b4096d287515d5450e69c15b96_2149.ko" in 1550usr/510sys/2322real ms.
Pass 5: starting run.
pid 52
  pid 12573
Done
Pass 5: run completed in 10usr/80sys/467real ms.

つぎはnsenterでdockerコンテナのpid名前空間に入り、そこでスクリプトを実行してみます。 まずはnsenter。

$ sudo nsenter -t 12494 -p

systemtapスクリプトを実行。

# ./tools/systemtap/bin/stap -v -g ./pid.stp 52                                                                                                                                                  
Pass 1: parsed user script and 113 library scripts using 69840virt/36220res/5048shr/31492data kb, in 100usr/10sys/105real ms.
Pass 2: analyzed script: 2 probes, 2 functions, 1 embed, 0 globals using 70632virt/37252res/5304shr/32284data kb, in 0usr/0sys/3real ms.
Pass 3: translated to C into "/tmp/stapRubZUb/stap_96165e2d709fb6669fa3a3854fe6f030_2146_src.c" using 70632virt/37252res/5304shr/32284data kb, in 0usr/0sys/0real ms.
Pass 4: compiled C into "stap_96165e2d709fb6669fa3a3854fe6f030_2146.ko" in 1540usr/480sys/2312real ms.
Pass 5: starting run.
pid 52
  pid 12573
Done
Pass 5: run completed in 20usr/80sys/496real ms.

nsenterの場合、ファイルシステムは元の名前空間を使ってるので/procが完全に切り替わってません。なので、/procまで完全に切り離した状態でやってないのですが多分動くはず。。。

以下、systemtapのめもをば。 systemtapは実行時にコマンドライン引数を渡すことができます。今回は./pid.stp 52という感じでpidを渡しています。コマンドライン引数をprobeのところで使用するには$1や@1などで使用できます。$や@は型で、$は整数、@は文字列です。

このようにc言語で書いた関数にパラメータを渡しています。

probe begin {
        /* pass pid that is command line argument */
        show_pidtree($1)
        exit()
}

今回作ったスクリプトc言語の関数を組み込んでいて、これも引数を取ることができます。使えるのはstringかlong型です。

function show_pidtree:long(pn:long) %{
%}

このスクリプトだとlong型のpnという引数を受け取ります。 ただ、引数はpnという名前ではアクセスできなくて(undefinedで怒られる)、STAP_ARGというprefixが必要です。 そのため、スクリプトでは下記のようにSTAP_ARG_pnとして引数のpnにアクセスしています。

        int pid_nr = STAP_ARG_pn;

systemtapスクリプトでは多少癖はありますが、c言語を使えるので便利ですね。

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

Linux: pid numberからtask_structの取得

pid numberからtask_structの取得のめも

pid番号から直接task_structは取得できないので、pid構造体の取得 -> task_struct構造体の取得という流れになる。 pid名前空間を気しなくて良い場合は、find_get_pid()でpid構造体を取得し、pid_task()でtask_structを取得できる。pid名前空間が重要になる場合は、find_get_pid()ではなくてfind_ge_pid()を使ってpid構造体を取得する。

  static struct task_struct *find_task_by_pid(int nr) 
  {
          struct pid *pid = find_get_pid(nr);
          if (!pid) {
                  pr_warn("couldn't find pid %d's task\n", nr);
                  return NULL;
          }   
  
          return pid_task(pid, PIDTYPE_PID);
  }   

( ´ー`)フゥー...

エクストリームプログラミング

エクストリームプログラミング

Ansibleのget_urlでログインが必要なwebページからファイルをダウンロードする

Ansibleのplaybookを書いていて、ファイルをダウンロードする前にログインが必要な場合にどうすればよいのかを調べたのでめも。

使用してるAnsibleのバージョンは2系です。

curlで例えると、--userでIDとパスワードを↓のように渡すのをansibleでどうやるかってところです。

curl --user username:password -L https://www.example.com/foobar.zip -o foobar.zip

で、解決策は我らがstackoverflow.comにありました。

stackoverflow.com

必要なのはforce_basic_auth=yesでした。

- name: Download some app
  local_action:
    get_url force=yes
      url=https://www.example.com/foobar.zip
      dest=ダウンロード先は適当に
      url_password={{ lookup('env', 'WEB_PASSWD') }}
      url_username={{ lookup('env', 'WEB_USERNAME') }} 
      force_basic_auth=yes 

get_urlのドキュメントによるとforce_basic_authは2.0からサポートされたようですね。

( ´Д`)=3 フゥ

初めてのAnsible

初めてのAnsible

Linux netns: グローバルなNet Namespace

グローバルなnsproxyに設定するNet Namespaceの変数のinit_nsめも

これはコンパイル時にはリストしか初期化していないので、その他のデータはカーネルの起動時に初期化してます。

 35 struct net init_net = {
 36         .dev_base_head = LIST_HEAD_INIT(init_net.dev_base_head),
 37 };

グローバルなNet Namespaceの初期化はnet_ns_init()で行います。 net_ns_init()で行う主要な処理はこちらの記事で書いたものを使用します。

kernhack.hatenablog.com

たとえば、net_generic構造体の設定、register_pernet_subsys()でコンストラクタ・デストラクタの登録などです。 その他にsetup_net()によるリストや参照カウンタ等の設定もありますが、これはこちらの記事で調べています。

kernhack.hatenablog.com

というわけで、init_nsに関してはこれと言った特別なことはしていませんでした。