Open83

Linux Parallel Marathon

暫定ルール案ドラフトたたき台WIP

  • 方針
    • 全力疾走!
    • スパルタ!
    • 効率を考えない!
  • 進め方
    • ぜーんぶ写経する
    • 練習問題もやる?
    • 1章ごとに休憩
    • 1章内は休憩しない? (トイレやその他イベントは例外)
    • てきとーな単位で交代(ルールが単純なのと、コミット交代がlogになるから)
  • 食事
    • ごはんはなるべく買いだしておく
    • つまみ食いできるようにしておくのがのぞましい(長く休憩すると復帰が困難)
  • ディレクトリはチャプター単位で連番でまとめる
    • ファイル名は単純な連番にする
      • chap01/1.c, chap01/2.c, ``chap02/1.c` のように。
      • どういう内容のファイルなのかについては、コメントで書くのがいいと思う(ファイル名で表現しようとするとつらい)
  • 共有環境
  • その他
    • iPadも用意しておく
    • ヘッドホンは頭がやばい
    • イヤホンも長時間は耳がやばい

メモ

・12hやろうとすると、BTのイヤホンマイクの充電がもたない

12h走った結果

  • 8:50-20:50(12h達成!)
  • 7章まで!(p.90まで、まえがき含む)
  • 休憩は、章ごとに10分

細かめタイムライン

TODO: あとで書く

12h走った感想

  • GoSysで鍛えられたから結構いろいろできた
  • 疲れたけど、まだまだいけるよ!
  • 節ごとに写経交代も楽で良かった
  • 章ごとに10分休憩はちょうどよい感じだと思う
  • 大休憩を取らないのは正解だったと思う
  • ヘッドホンの電池はギリギリもった(ぎりぎり)
  • C言語的なところでハマりがち! 逆に、それ以外はハマってないとおもう。
  • プロセスとかファイルディスクリプタあたりを復習できたのもでかい

感想

  • 途中寝落ちしてからの復帰したが、体の調子が良くなっていたので、やはり昼寝は大事
  • 12hで7章くらいしか出来なかったのはなんか意外
    • 個人的には10章くらい行けるのではと思った
  • 10分休憩は大事
  • 章が多いところだとしんどいかも
    • 120分は集中がもたないかも。
  • C言語に踊らされることが多い
  • 問題は結構飛ばした

第1章 プロセスとfork

11:45
105分

プロセス表ってなんだろうね。
ps コマンドで見れるリストかな?

fork()したあとの親子のプロセスの再開順序は、保証されていない…!

2回forkのイメージ

-------|---------
  |         |
  |         -----
  |
  -----|---------
            |
            -----

親プロセスでのfork() で返ってくるのは子プロセスのid

あれ? 親プロセスは子プロセスはいっぱいあるパターンだと、どのid返すんだろう?

fork()のシステムコールを実行するときには、一度の子プロセスしか生まれないので、そのidが返ってくるということ

fork()の時点ですでに「並行処理」という感じが重要な気がした

もう入り口に立っている!

並行処理って、異なる処理を並べて実行しているという意味が強かったが、
fork()だけでも並行処理と言えそう?

わからん

wait() に statusのポインタを渡しているが、statusの値が0だったらwaitするのを終了する? というわけでは無さそう。
一体、どういう判断でwaitしているのか

→ 子プロセスの exit() の値が返ってくる


#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

int main() {
    int status = 0;

    if(fork() == 0){
        printf("子プロセス pid = %d\n", getpid());
        printf("子プロセスステータス %d\n", status);
        sleep(2);

    }else {
        printf("ステータス %d\n", status);
        printf("wait ステータス %d\n", wait(&status));
        printf("ステータス %d\n", status);
        printf("親プロセス\n");
    }
}

子プロセスでのexit()ライブラリ関数は、任意の整数値を親に返せるのか!

終了コードが渡せる。

ハンドリングできるわけか。

fork() しない=子プロセスを生成しない場合でwaitしたら…?

int main() {
    int status;
    pid_t pid;

        pid = wait(&status);
        printf("親プロセス\n");
        printf("子プロセスのid = %d\n", pid);
        printf("子プロセス終了時の値は = %04x\n", status);
}
親プロセス
子プロセスのid = -1
子プロセス終了時の値は = e5e18600

これだと、メインプロセスが終わってしまい、ターミナルの方は受付できるようになっているが、
裏ではプロセス2が生きていて、いきなりターミナルに文字列が出力される


#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>

int main() {
    int status;

    if(fork() == 0){
        printf("子プロセス1\n");
        sleep(2);
        printf("子プロセス1 done\n");
    }
    else {
        if(fork() == 0){
            printf("子プロセス2\n");
            sleep(10);
            printf("子プロセス2 done\n");
        } else {
            wait(&status);
            printf("子プロセス終わったよ\n");
        }
    }
}
~/Projects/LinuxParalellMarathon/chap01/1.4/q !+main (130) 15.8s 10:26:16$ gcc 1-5.c; ./a.out
子プロセス2
子プロセス1
子プロセス1 done
子プロセス終わったよ
~/Projects/LinuxParalellMarathon/chap01/1.4/q !+main 2.0s 10:26:19$ 
~/Projects/LinuxParalellMarathon/chap01/1.4/q !+main 7.9s 10:26:24$ 
~/Projects/LinuxParalellMarathon/chap01/1.4/q !+main 8.3s 10:26:25$ 子プロセス2 done

wait() は先に終わった方のプロセスを見てくれる。
そのときのstatusは、それぞれのプロセスのexit()の引数によって変わる

//
// Created by jun on 2021/08/01.
//

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int status;

    if(fork() == 0){
        printf("子プロセス1\n");
        sleep(5);
        printf("子プロセス1 done\n");
        exit(1);
    }
    else {
        if(fork() == 0){
            printf("子プロセス2\n");
            sleep(1);
            printf("子プロセス2 done\n");
            exit(255);
        } else {
            wait(&status);
            printf("子プロセス終了時の値は = %04x\n", status);
            wait(&status);
            printf("子プロセス終了時の値は = %04x\n", status);
            printf("子プロセス終わったよ\n");
        }
    }
}

子プロセス1
子プロセス2
子プロセス2 done
子プロセス終了時の値は = ff00
子プロセス1 done
子プロセス終了時の値は = 0100
子プロセス終わったよ

wait()で同期をとる

非決定的から決定的な動作に!

#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

int main() {
    int status;
    if (fork() == 0) {
        // 子
        if (fork() == 0) {
            // 孫
            printf("孫プロセス PID = %d Done\n", getpid());
        } else {
            // 子
            wait(&status);
            printf("子プロセス PID = %d Done\n", getpid());
        }
    } else {
        // 親
        wait(&status);
        printf("親プロセス PID = %d Done\n", getpid());
    }
}
$ ./a.out
孫プロセス PID = 3494 Done
子プロセス PID = 3493 Done
親プロセス PID = 3491 Done

わからん とばした!

問題2-4と問題2-5はC言語つらかったので飛ばした!

わからん

exit() のステータスコードは、なぜ 16進数の4桁で貰うようになっているのか

第2章 プロセスの変身とシェル

12:30

わからん execl()の2番めの引数(arg0)の意味とは?

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("cal 1 2020 に変身\n");

    execl("/usr/bin/cal", "cal", "1", "2020", (char *) 0);
    execl("/usr/bin/cal", "hgoehgioehgo", "1", "2020", (char *) 0);

}

関数 execl(), execlp(), execle() の const char *arg とそれに続く省略部分は arg0, arg1, ..., argn とみなされる。

これらには、実行されるプログラムで利用可能な引き数のリストを指定する (引き数のリストは ヌルで終端された文字列へのポインターから構成される)。

慣習として、最初の引き数は、実行されるファイル名 へのポインターにする

引き数のリストは必ず NULL で終わらなければならず、これらの関数は可変長引き数関数なので、 このポインターは (char *) NULL とキャストしなければならない。

わからん

リストと配列(Vector)の違いがいまいちわかっていない

→ 連結リストと配列の違い

strtokの動き

ポインタって自由自在!


// http://www9.plala.or.jp/sgwr-t/lib/strtok.html
#include        <stdio.h>
#include        <string.h>

int main(void) {
    char str[] = "ABCD ef.1234.G";
    char str2[] = "Hello World";
    char *tp;

    puts(tp);

    // tp は トークンの先頭を示す「ポインタ」
    tp = strtok(str, " .");
    puts(tp);

    tp = strtok(NULL, " .");
    puts(tp);

    tp = strtok(str2, " ");
    puts(tp);
}

execしても子プロセスにはならない! 本当に変身している!

確かめるには、PIDを確認すればいい!

$ gcc chap02/2.1/ex1.c;./a.out
コマンドを入力してください? sleep 300
ex1 は sleep に変身します
PID=5656

# 別ターミナルで ps sleep (で、Tabキー)で探す
$ ps 5656
  PID   TT  STAT      TIME COMMAND
 5656 s000  S+     0:00.00 sleep 300

getsはバッファオーバーフロー(バッファオーバーラン)が起こるので禁止!

『ふつうのLinuxプログラミング』p.113みてね

シェルコマンドは区別しよう!

$ which exit
exit: shell built-in command
$ which alias
alias: shell built-in command

わからん! プロセスにsuspendを送ったあと、resumeする方法が!

$ sleep 10
^Z
zsh: suspended  sleep 10
$ ps 6606
  PID   TT  STAT      TIME COMMAND
 6606 s001  T      0:00.00 sleep 10

bashの意味?

bashの「Bourne Again Shell」は「Born Again(生まれ変わり)」とかけていることからも分かるように、bashとの互換性を持ちながら、他のシェルで提供されていたさまざまな機能を取り込んで新しく作り直されたシェルである。

https://www.atmarkit.co.jp/ait/articles/1607/28/news039.html

suspendからの復帰

~/Projects/LinuxParalellMarathon/chap06/6.3 main .7s 20:56:40$ sleep 100 
^Z
zsh: suspended  sleep 100
~/Projects/LinuxParalellMarathon/chap06/6.3 main (148) 3.0s 20:56:49$ jobs
[1]  + suspended  sleep 100
~/Projects/LinuxParalellMarathon/chap06/6.3 main 0s 20:56:50$ fg %sleep
[1]  + continued  sleep 100
^C

第3章 ファイル入出力

14:30

O_CREATとかのやつ

// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}

// Create creates or truncates the named file. If the file already exists,
// it is truncated. If the file does not exist, it is created with mode 0666
// (before umask). If successful, methods on the returned File can
// be used for I/O; the associated file descriptor has mode O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*File, error) {
	return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}```

forkすることで、fd表もコピーされる

fd1を作った後で、fork()するので、子プロセスも親プロセスも同じファイルを見てる

fd2の方は、fork()したあとの世界なので、それぞれ別のファイルを見ている

//
// Created by jun on 2021/08/01.
//

#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    int fd1, fd2;
    int status;
    int count;
    char buf[20];


    if((fd1 = open("oldfile", O_RDONLY)) == -1 ) exit(1);
    if (fork() == 0) { // fork()することで、fd表もコピーされて、fd1が同じファイル(oldfile)を見ている
        if((fd2 = creat("newfile1", 0664)) == -1) exit(1);
        while((count = read(fd1, buf, 20)) != 0) {
            write(fd2, buf, count);
            write(fd2,"\n", 1);
            write(1, "child\n", 6);
            sleep(1);
        }
    } else {
        if((fd2 = creat("newfile2", 0664)) == -1) exit(1);
        while((count = read(fd1, buf, 20)) != 0) {
            write(fd2, buf, count);
            write(fd2, "\n", 1);
            write(1, "parent\n", 7);
            sleep(1);
        }
        wait(&status);
    }
}

とばした!

問題3-3はとばした!

わからん

片方のプロセスでしかコピーができない(newfile3, newfile4のどちらか)

この理由は、わからない



#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    FILE *rfp, *wfp;
    char c;

    if ((rfp=fopen("oldfile", "r")) == NULL){
        exit(1);
    }

    if(fork() == 0) {
        if((wfp=fopen("newfile3", "w")) == NULL)
        {
            exit(1);
        }
        while((c=getc(rfp)) != EOF) {
            putc(c,wfp);
        }
        fclose(rfp);
        fclose(wfp);
    }
    else
    {
        if((wfp= fopen("newfile4", "w")) == NULL) {
            exit(1);
        }
        while((c=getc(rfp)) != EOF) {
            putc(c, wfp);
        }
        fclose(rfp);
        fclose(wfp);
    }
}

わからん

whileループだとCPU権利は、そのプロセスのままになるのか?

sleepとか入れば、スイッチされるかなととか思ったんだけど。。。

コンテキストスイッチのきっかけがわかれば、わかるかも?

わからん

GNU-Cライブラリの、入力バッファと出力バッファって可視化できないの?

わからん

ungetcをやると、なぜか両方のファイルが書き込める


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    FILE *rfp, *wfp;
    char c;

    if ((rfp=fopen("oldfile", "r")) == NULL){
        exit(1);
    }
    c=getc(rfp);
    printf("%c\n", c);
    // 読み込んだ文字を入力バッファへ戻す
    ungetc(c,rfp);

    if(fork() == 0) {
        if((wfp=fopen("newfile3", "w")) == NULL)
        {
            exit(1);
        }
        while((c=getc(rfp)) != EOF) {
            putc(c,wfp);
        }
        fclose(rfp);
        fclose(wfp);
    }
    else
    {
        if((wfp= fopen("newfile4", "w")) == NULL) {
            exit(1);
        }
        while((c=getc(rfp)) != EOF) {
            putc(c, wfp);
        }
        fclose(rfp);
        fclose(wfp);
    }
}

printf()fprintf()は、ラインバッファに入れられる!

なので、

\nをしない場合は、ラインバッファに貯まる → forkしたあとのprintf()で、親子それぞれのプロセスで使える!

バッファを意識できるな!


#include <unistd.h>
#include <stdio.h>

int main() {
    printf("Before fork"); // \n をつけてない!
    fork();
    fork();
    fork();
    printf("プロセス id = %d\n", getpid());
}

こうなる

$ gcc chap01/1.2/q/1-2.c; ./a.out 
Before forkプロセス id = 9244
Before forkプロセス id = 9247
Before forkプロセス id = 9246
Before forkプロセス id = 9245
Before forkプロセス id = 9249
Before forkプロセス id = 9250
Before forkプロセス id = 9248                                                             
Before forkプロセス id = 9251

fflushしないパターン

before fork: がラインバッファとして出力バッファに溜め込まれる。
親と子のプロセスの方で\nを付けているので、flushされる。

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("before fork : ");
    if(fork() == 0){
        printf("child process\n");
    }else{
        printf("parent process\n");
    }
}
before fork : parent process
before fork : child process

fflushするパターン

こちらは、forkする前にfflushしてしまっているので、1回分しか表示されない
(子プロセスにバッファが引き継がれない)

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("before fork : ");
    fflush(stdout);
    if(fork() == 0){
        printf("child process\n");
    }else{
        printf("parent process\n");
    }
}
before fork : parent process
child process

fork()はバッファも引き継いでいる??

https://docs.oracle.com/cd/E19455-01/806-2731/process-71145/index.html

fork(2) 関数または exec(2) 関数によって生成される新しいプロセスは、3 つの標準ファイル stdin、stdout、stderr を含めた開いているすべてのファイル記述子を親から継承します。親プロセスが、子プロセスの出力よりも先に表示しなければならない出力をバッファリングしている場合、fork(2) の前にバッファをフラッシュしなければなりません。

fork()では、バッファもコピーされるので、先にフラッシュしたほうがいいよ!

https://docs.oracle.com/cd/E19205-01/820-1201/aetcu/index.html

fork(2) 関数または exec(2) 関数によって生成される新しいプロセスは、3 つの標準ファイル stdin、stdout、stderr を含めた開いているすべてのファイル記述子を親から継承します。

親プロセスが、子プロセスの出力よりも先に表示しなければならない出力をバッファリングしている場合、fork(2) の前にバッファをフラッシュしなければなりません

第4章 リダイレクトとパイプ

16:30

ファイル記述子をコピーするシステムコールdup()が用意されている

これは初見

14:41から開始

#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd;

    fd = open("newfile", O_WRONLY | O_CREAT, 0664);

    close(1); // stdout を 開放する

    // dupは開放されているエントリの中で、もっと若い番号のfdに複製するぞ!
    dup(fd); // close(1)により、fd=1が欠番だから、fd=1はnewfileにつながる

    execlp("cat", "cat", (char *) 0);
}

わからん! 問題4-1

stdout(fd=1)をcloseする前にどっかに保存しとけばいいって感じだと思うんだけど、確認方法?がよくわからn

とばした! 問題4-2、問題4-3、問題4-4

親子でプロセス間通信

子プロセス がファイルを読み取り、パイプの書き込み用のFDに書き込む
親プロセスがパイプの読み取り用のFDから読み取り、標準出力に書き込む


//
// Created by jun on 2021/08/01.
//

#include <unistd.h>
#include <fcntl.h>

int main() {
    int fd[2], rfd;
    char c;

    pipe(fd);

    if(fork() == 0){
        close(fd[0]);
        rfd = open("oldfile", O_RDONLY);
        while (read(rfd, &c, 1) != 0) {
            write(fd[1], &c, 1);
        }
        close(fd[1]);
        close(rfd);
    }else {
        close(fd[1]);
        while(read(fd[0], &c, 1) != 0){
            write(1, &c, 1);
        }
        close(fd[0]);
    }
}

closedupのコンボがリダイレクトであると意識できるといい感じ

defunct 【dɪfˈʌŋ(k)t】

わからん! mknod ってなんの略?

S_IFIFOで名前付きファイルを作れる

mknodは特殊ファイル(デバイスファイル)及びFIFO(名前付きパイプ)などを作成します。
mknodコマンドやmkfifoコマンドなどで利用されるシステムコールとなります。
mknodは「make node」の略です。

http://capm-network.com/?tag=C言語システムコール-mknod

pipe感あって最高! ブロックされるの最高!

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>


int main() {
    int pfd;
    char c;

    if ((pfd = open("mypipe", O_RDONLY)) == -1) {
        exit(1);
    }

    while (read(pfd, &c, 1) != 0) {
        write(1, &c, 1);
    }
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>


int main() {
    int pfd, rfd;
    char c;

    if ((pfd = open("mypipe", O_WRONLY)) == -1) {
        exit(1);
    }

    if ((rfd = open("oldfile", O_RDONLY)) == -1) {
        exit(1);
    }

    while (read(rfd, &c, 1) != 0) {
        write(pfd, &c, 1);
    }
}

4.7のクラサバをパイプで作るやつ、マジでアガるわ!

第5章 プロセス間通信

18:00

ipcs は IPC Status

IPCにもいろいろあるわけで

$ ipcs
IPC status from <running system> as of Sun Aug  1 17:33:48 JST 2021
T     ID     KEY        MODE       OWNER    GROUP
Message Queues:

T     ID     KEY        MODE       OWNER    GROUP
Shared Memory:
m  65536 0x00fbf941 --rw-------   mohira    staff
m  65537 0x51059e34 --rw-------   mohira    staff

T     ID     KEY        MODE       OWNER    GROUP
Semaphores:
s  65536 0x00fbf942 --ra-------   mohira    staff
s  65537 0x00fbf943 --ra-------   mohira    staff
s  65538 0x00fbf944 --ra-------   mohira    staff
s  65539 0x00fbf945 --ra-------   mohira    staff
s  65540 0x00fbf946 --ra-------   mohira    staff
s  65541 0x00fbf947 --ra-------   mohira    staff
s  65542 0x00fbf948 --ra-------   mohira    staff
s  65543 0x00fbf949 --ra-------   mohira    staff
s  65544 0x51059e33 --ra-------   mohira    staff
s  65545 0x51059e35 --ra-------   mohira    staff

メッセージキューのGlobal感が強い

IPCの種類

  • メッセージキュー
  • 共有メモリ

とばした! 問題5-2

第6章 相互排除とセマフォ

セマフォのup-downは、手旗信号やん!

「赤上げて! 白上げて! 白下げないで、赤下げる」のup-down

goのsync.WaitGroupはカウンタセマフォじゃん

up -> wg.Add()

down -> wg.Done()

メッセージキューもセマフォも共有メモリも、扱うシステムコールが似ている。
相似形。

第7章 スレッドによる並行処理

ゴルーチン修行の成果が出ている! 完全にわかる(ただし、ポインタの指定方法はのぞく)

// Thread1: Func1(void)
// Thread2: Func2(void)

#include <stdio.h>
#include <pthread.h>

void Func1(void) {
    printf("thread1\n");
}

void Func2(void) {
    printf("thread2\n");
}

int main() {
    pthread_t th_id1, th_id2;
    int st1, st2;

    st1 = pthread_create(&th_id1, NULL, (void *(*)(void *)) Func1, NULL);
    if (st1 != 0) {
        printf("can't create thread1\n");
        return 1;
    }

    st2 = pthread_create(&th_id2, NULL, (void *(*)(void *)) Func2, NULL);
    if (st2 != 0) {
        printf("can't create thread2\n");
        return 1;
    }

    pthread_join(th_id1, NULL);
    pthread_join(th_id2, NULL);

    printf("All done\n");
}

fork-joinモデル

『Go言語による並行処理』p.39

pthreadでスレッド間でコミュニケーションするコード書くことにより、Goのチャネルが楽ってのがわかってきた感じあるな。

わからん

//
// Created by jun on 2021/08/01.
//

#include <stdio.h>
#include <pthread.h>

double Func4(int *x){
    printf("new thread: x = %d\n", *x);
    printf("%f\n", (double )(*x)*2);
    return (double)(*x)*2;
}

int main(int argc, char *argv[]){
    pthread_t th_id;
    int st, x;
    double y;
    x = 5;
    st = pthread_create(&th_id, NULL, (void *(*)(void *)) Func4, &x);
    if (st != 0){
        return 1;
    }
    // void ** がよくわからない
    pthread_join(th_id, (void **)&y);
    printf("answer : y = %f\n", y);
    return 0;
}

なぜか、yの結果が0のままになる。
計算結果が戻ってこない?

new thread: x = 5
10.000000
answer : y = 0.000000

第8章 mutexによる相互排除

第9章 条件変数による同期制御

第10章 タスクスケジューリング

第11章 シグナルによるイベント処理

第12章 シグナルによる例外処理

ログインするとコメントできます