Open83

Linux Parallel Marathon

mohiramohira
mohiramohira

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

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

メモ

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

mohiramohira

12h走った結果

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

細かめタイムライン

TODO: あとで書く

12h走った感想

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

感想

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

第1章 プロセスとfork

11:45
105分

jnuankjnuank

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

jnuankjnuank

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

mohiramohira

2回forkのイメージ

-------|---------
  |         |
  |         -----
  |
  -----|---------
            |
            -----
jnuankjnuank

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

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

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

mohiramohira

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

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

jnuankjnuank

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

jnuankjnuank

わからん

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");
    }
}
mohiramohira

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

終了コードが渡せる。

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

jnuankjnuank

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
jnuankjnuank

これだと、メインプロセスが終わってしまい、ターミナルの方は受付できるようになっているが、
裏ではプロセス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

jnuankjnuank

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
子プロセス終わったよ
mohiramohira

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
mohiramohira

わからん とばした!

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

jnuankjnuank

わからん

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

mohiramohira

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

12:30

mohiramohira

わからん 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 とキャストしなければならない。

jnuankjnuank

わからん

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

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

mohiramohira

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);
}
mohiramohira

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
mohiramohira

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

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

mohiramohira

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

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

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

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

bashの意味?

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

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

jnuankjnuank

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
mohiramohira

第3章 ファイル入出力

14:30

mohiramohira

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)
}```
jnuankjnuank

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);
    }
}
jnuankjnuank

わからん

片方のプロセスでしかコピーができない(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);
    }
}
mohiramohira

わからん

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

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

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

mohiramohira

わからん

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

jnuankjnuank

わからん

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);
    }
}
mohiramohira

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
jnuankjnuank

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()はバッファも引き継いでいる??

jnuankjnuank

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

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

mohiramohira

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

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

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

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

mohiramohira

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

16:30

jnuankjnuank

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

これは初見

mohiramohira
#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);
}
mohiramohira

わからん! 問題4-1

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

jnuankjnuank

親子でプロセス間通信

子プロセス がファイルを読み取り、パイプの書き込み用の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]);
    }
}
mohiramohira

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

mohiramohira

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);
    }
}
mohiramohira

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

mohiramohira

第5章 プロセス間通信

18:00

mohiramohira

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
mohiramohira

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

mohiramohira

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

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

mohiramohira

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

up -> wg.Add()

down -> wg.Done()

jnuankjnuank

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

mohiramohira

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

mohiramohira

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

// 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");
}
mohiramohira

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

jnuankjnuank

わからん

//
// 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