『Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識』第4章[前篇]
第4章 プロセススケジューラ[前篇]
はじめに
武内 覚 著『Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識』をテキストとして学習した記録です。
本書を読んで筆者が解釈した内容について記述しています。
なので本書の内容の間違った解釈やあるいは単純に間違った記述がある可能性があります。
プロセススケジューラとは
一つのCPUで同時に処理できるプロセスは一つだけ。一つのCPUで複数のプロセスを並行して処理する必要がある場合、処理する対象のプログラムを適切な単位(時間,タイムスライスと呼ぶ)に区切ってそれぞれのプロセスを順番に処理していく仕組みがある。この仕組みをプロセススケジューラと呼ぶ。
この章ではプロセススケジューラの働きを確認するためのプログラムを作成し実際の動きを確認する。
一つのCPUが処理するプロセスについて確認するが、現代のPCのCPUはマルチコアが実装されている場合が多い。
マルチコアのCPUの場合、Linuxは各コアをそれぞれ独立したCPUとみなす。これを論理CPUと呼ぶ。
コンテキストスイッチ
CPUがプロセススケジューラによって処理する対象のプロセスを切り替えることをコンテキストスイッチと呼ぶ。
コンテキストスイッチは通常タイムスライスによって行われる。
コンテキストスイッチが発生するとプロセスの途中で処理が中断されるため、CPUがマルチプロセスで処理を行なっている場合は各プロセスは必ずしも時間に比例して進捗する訳ではないことを考慮する必要がある。
テストプログラム
#include <sys/types.h> #include <sys/wait.h> #include <time.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <err.h> #define NLOOP_FOR_ESTIMATION 1000000000UL #define NSECS_PER_MSEC 1000000UL #define NSECS_PER_SEC 1000000000UL static inline long diff_nsec(struct timespec before, struct timespec after) { return ((after.tv_sec * NSECS_PER_SEC + after.tv_nsec) - (before.tv_sec * NSECS_PER_SEC + before.tv_nsec)); } static unsigned long loops_per_msec() { struct timespec before, after; // 処理が開始前の時間をbeforeに代入 clock_gettime(CLOCK_MONOTONIC, &before); // 適当な回数loopするだけのプログラム unsigned long i; for (i = 0; i < NLOOP_FOR_ESTIMATION; i++) ; // 処理が終了後の時間をafterに代入 clock_gettime(CLOCK_MONOTONIC, &after); int ret; // 何回loopを回せば1ミリ秒かかるかを推定 return NLOOP_FOR_ESTIMATION * NSECS_PER_MSEC / diff_nsec(before, after); } static inline void load(unsigned long nloop) { unsigned long i; for(i = 0; i < nloop; i++) ; } static void child_fn(int id, struct timespec *buf, int nrecord, unsigned long nloop_per_resol, struct timespec start) { int i; for(i = 0; i < nrecord; i++){ struct timespec ts; load(nloop_per_resol); clock_gettime(CLOCK_MONOTONIC, &ts); buf[i] = ts; } for(i = 0; i < nrecord; i++){ printf("%d\t%ld\t%d\n", id, diff_nsec(start, buf[i])/NSECS_PER_MSEC, (i + 1) * 100 / nrecord); } exit(EXIT_SUCCESS); } static void parent_fn(int nproc) { int i; for(i = 0; i < nproc; i++) wait(NULL); } static pid_t *pids; int main(int argc, char *argv[]) { // returnを初期化 int ret = EXIT_FAILURE; if(argc < 4){ fprintf(stderr, "usage: %s <nproc> <total[ms]> <resolution[ms]>\n", argv[0]); exit(EXIT_FAILURE); } // ascii to integer int nproc = atoi(argv[1]); int total = atoi(argv[2]); int resol = atoi(argv[3]); if(nproc < 1){ fprintf(stderr, "<nproc>(%d) should be >= 1\n", nproc); exit(EXIT_FAILURE); } if(total < 1){ fprintf(stderr, "<total>(%d) should be >= 1\n", total); exit(EXIT_FAILURE); } if(resol < 1) { fprintf(stderr, "<resol>(%d) should be >= 1\n", resol); exit(EXIT_FAILURE); } if(total % resol) { fprintf(stderr, "<total>(%d) should be multiple of <resolution>(%d)\n", total, resol); exit(EXIT_FAILURE); } // 統計取得の回数 int nrecord = total / resol; // メモリを確保する struct timespec *logbuf = malloc(nrecord * sizeof(struct timespec)); if(!logbuf) err(EXIT_FAILURE, "malloc(logbuf) failed"); // ちょうど1ミリ秒かかる負荷の測定中 puts("estimating workload which takes just one milisecond"); unsigned long nloop_per_resol = loops_per_msec() * resol; // 測定終了 puts("end estimation"); fflush(stdout); pids = malloc(nproc * sizeof(pid_t)); if(pids == NULL){ warn("malloc(pids) failed"); goto free_logbuf; } struct timespec start; clock_gettime(CLOCK_MONOTONIC, &start); int i, ncreated; for(i = 0, ncreated = 0; i < nproc; i++, ncreated++){ pids[i] = fork(); if(pids[i] < 0){ goto wait_children; }else if(pids[i] == 0){ // children child_fn(i, logbuf, nrecord, nloop_per_resol, start); /* shouldn't reach here */ } } ret = EXIT_SUCCESS; // parent wait_children: if(ret == EXIT_FAILURE) for(i = 0; i < ncreated; i++) if(kill(pids[i], SIGINT) < 0) warn("wait() faild."); free_pids: free(pids); free_logbuf: free(logbuf); exit(ret); }