Linux Parallel Marathon
README
底本
リポジトリ
#LinuxParallelMarathon
暫定ルール案ドラフトたたき台WIP
- 方針
- 全力疾走!
- スパルタ!
- 効率を考えない!
- 進め方
- ぜーんぶ写経する
- 練習問題もやる?
- 1章ごとに休憩
- 1章内は休憩しない? (トイレやその他イベントは例外)
- てきとーな単位で交代(ルールが単純なのと、コミット交代がlogになるから)
- 食事
- ごはんはなるべく買いだしておく
- つまみ食いできるようにしておくのがのぞましい(長く休憩すると復帰が困難)
- ディレクトリはチャプター単位で連番でまとめる
- ファイル名は単純な連番にする
-
chap01/1.c
,chap01/2.c
, ``chap02/1.c` のように。 - どういう内容のファイルなのかについては、コメントで書くのがいいと思う(ファイル名で表現しようとするとつらい)
-
- ファイル名は単純な連番にする
- 共有環境
- Zoomで行いたい
- 画面共有を複数人同時にやることで切り替え負荷を下げる
- てきとーに配信する
- 非公開で配信 → アーカイブ
- その他
- 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(2)
システムコール
『システムプログラミング入門』 エクササイズ3.9(p.46) もやるとよさげ
わからん
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
unistd.h
とは?
UNIx STanDard Header file
という事です。
stdlib.h ですと、
STanDard LIBrary Header file
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
strcmp
文字列の比較には
わからん! プロセスに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との互換性を持ちながら、他のシェルで提供されていたさまざまな機能を取り込んで新しく作り直されたシェルである。
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()はバッファも引き継いでいる??
fork(2) 関数または exec(2) 関数によって生成される新しいプロセスは、3 つの標準ファイル stdin、stdout、stderr を含めた開いているすべてのファイル記述子を親から継承します。親プロセスが、子プロセスの出力よりも先に表示しなければならない出力をバッファリングしている場合、fork(2) の前にバッファをフラッシュしなければなりません。
fork()
では、バッファもコピーされるので、先にフラッシュしたほうがいいよ!
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]);
}
}
close
とdup
のコンボがリダイレクトであると意識できるといい感じ
defunct 【dɪfˈʌŋ(k)t】
わからん! mknod ってなんの略?
S_IFIFOで名前付きファイルを作れる
mknodは特殊ファイル(デバイスファイル)及びFIFO(名前付きパイプ)などを作成します。
mknodコマンドやmkfifoコマンドなどで利用されるシステムコールとなります。
mknodは「make node」の略です。
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章 相互排除とセマフォ
セマフォ(Semaphore; 信号機)
信号機っていっても、横断歩道の信号機じゃなくて、手旗信号って感じ!
↓↓ 画像見て!
Phone(電話)じゃないよ!
スマフォでもないよ!
セマフォのup-downは、手旗信号やん!
「赤上げて! 白上げて! 白下げないで、赤下げる」のup-down
sync.WaitGroup
はカウンタセマフォじゃん
goのup -> wg.Add()
down -> wg.Done()
メッセージキューもセマフォも共有メモリも、扱うシステムコールが似ている。
相似形。
at
はアタッチ、dt
はデタッチ!
shmat, shmdt – 共有メモリをアタッチまたはデタッチする
第7章 スレッドによる並行処理
わからん
key_tやpthread_tの t
って何だろう
→ typedefを表すサフィックスらしい
ゴルーチン修行の成果が出ている! 完全にわかる(ただし、ポインタの指定方法はのぞく)
// 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
自動変数xとは?
→ ローカル変数っぽい
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
_t
の謎