有名な本らしいので買ってみました
ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道
ふつうのLinuxプログラミング 第2版 Linuxの仕組みから学べるgccプログラミングの王道
ふつうのLinuxプログラミングをやってみる その1
https://qiita.com/uturned0/items/b9ae846f2aff5865c074
ふつうのLinuxプログラミングをやってみる その2
https://qiita.com/uturned0/items/56beac990cdd6f1059ed
ふつうのLinuxプログラミングをやってみる その3
https://qiita.com/uturned0/items/675092da8aa89c4b1ff0
その4
https://qiita.com/uturned0/items/8f5765cfc0f0be8a1981
その5
https://qiita.com/uturned0/items/ab97deb489c994a836da
その6
https://qiita.com/uturned0/items/b7df48e87ae9170f3698
その7
https://qiita.com/uturned0/items/263151cf7c83bff7fac1
その7−3
https://qiita.com/uturned0/items/3fbf3ed6bb2a47325f59
chapter 5
file descripter はただのint = streamの番号,IDのようなもの
どのprocessも3つのストリームがあって、file descripterが既知のものに鳴っている
fd 0 = standard input = マクロ名 STDIN_FILENO = path: /dev/stdin
fd 1 = standard output = マクロ名 STDOUT_FILENO = path: /dev/stdout
fd 2 = standard errour output = マクロ名 STDERR_FILENO = path: /dev/stderr
標準入力の標準はデフォルトという意味
$ cat
aaa
aaa
cat コマンドは引数なしだとstdinをstdout煮出すだけの動きをする。ストリームをin-outつないでるだけ。
$ cat < .bashrc
# .bashrc
# Source global definitions
...
ファイルを与えた時、catは実はファイルをreadするということをしていない!! シェルが勝手にファイルの中身をstdinとしてcatに渡しているだけ。 catは入力がファイルだということを知らない そうだったんだ!!!!!すげぇ!!
STDERRがあるのは、pipeしてると次のprocessのstdinに入って、気付かないから。stderrは特別に、その外に表示することになってる。アタマイイ。stdoutは機械向け、stderrは人間向け。なるほど。
read(2)
$ man 2 read
すると
名前
read - ファイルディスクリプタから読み込む書式
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); <---プロトタイプ宣言
ssize_t は符号付き整数型、size_tは符号なし整数型、つまり int/longなんだけど、kenelが違うとint型の仕様が違ったりするので、その際を隠蔽してる。必ずssize_tを使う。
C 言語のプロトタイプとは
先に名前、返り値だけ指定しといて、後で中身を書くやり方のやーつ
/* プロトタイプ宣言 */
void hello(void);
int main(void){
...長い処理
}
/* hello関数をあとで宣言 */
void hello(void){
printf("Hello, World!\n");
}
人間が読める文字を格納するchar配列の最後は '\0
にするのが慣習。printfがそう。しかしread()は違う。
read()→printf()するとヤバい。
この本のストリームとは
file descripterで表現され、read() write() を呼べるもののこと
fileをopenすると read/writeできるものが作られる。そこにはストリームがある。
pipe/network socketもストリームの仲間。
open(2)
$ man 2 open
名前
open, creat - ファイルやデバイスのオープン、作成を行う
書式
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
openはpathにつながるストリームを作成し、そのストリームを指す file descripterを返す ほう。socketみたいなものを開いて、そのIDを返すのか。なるほど
2nd argのflagは O_RDONLY, O_WRONLY, O_RDWR 文字通りr・w・rw
書き込む場合(O_WRONLY or O_RDWR) は追加のflagを一緒に2nd argに入れられるぽい
O_CREAT if not exist, create a file
O_EXCL if exist return error
O_TRUNC if exist make the file empty
O_APPEND 追記していく
close()
ファイルを閉じる=ストリームを破棄する=close()
kernelがprocess終了時に全streamは破棄してくれる。プロセスが使える同時ストリーム数は制限がある。
close()したほうがよさげ。
cat コマンドを作る
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
static void do_cat(const char *path);
static void die(const char *s);
int
main(int argc, char *argv[])
{
int i;
if (argc < 2) {
fprintf(stderr, "%s: file name not given\n", argv[0]);
exit(1);
}
for (i=1; i<argc; i++) {
do_cat(argv[i]);
}
exit(0);
}
# define BUFFER_SIZE 2048
static void
do_cat(const char *path)
{
int fd;
unsigned char buf[BUFFER_SIZE];
int n;
fd = open(path, O_RDONLY);
if (fd < 0) die(path);
for (;;) {
n = read(fd, buf, sizeof buf);
if (n < 0) die(path);
if (n == 0) break;
if (write(STDOUT_FILENO, buf, n) < 0) die (path);
}
if (close(fd) < 0) die(path);
}
static void
die(const char *s)
{
perror(s);
exit(1);
}
コンパイル
$ gcc -Wall -O2 cat.c
実行
$ ./a.out
./a.out: file name not given
ファイル渡す
$ ./a.out /etc/passwd
##
# User Database
#
...
複数ファイル
$ ./a.out /etc/hosts /etc/resolv.conf
##
# Host Database
#
...
# macOS Notice
#
# This file is not consulted for DNS hostname resolution, address
...
エラー
$ ./a.out notexist
notexist: No such file or directory
おお〜〜〜動いた〜〜〜〜!!!
argc
引数の数。
argv[0]
programの名前
For (;;)
泣いても喚いても繰り返せ
n==0
ファイルが終わったら。
$ man 2 read
RETURN VALUES
If successful, the number of bytes actually read is returned. Upon reading end-of-file, zero is returned.
ということなので、read()で0が来たらファイルの終わり。その続きに
Otherwise, a -1 is returned and
the global variable errno is set to indicate the error.
errorなら -1 がreturnとある. do_catの中のエラー処理はそんな漢字。
read(fd, buf, sizeof buf)
の意味
fd = open()したfileのstream. stream IDぽいintが入ってる。open()がIDを作ってreturnしてくれる
buf = fdから入ってきたデータ(byte列)を貯めるバッファ。
sizeof buf = buf変数の長さ。最大でここまでreadする(bufに貯める)オプション
defineしてるBUFFER_SIZEにしても動くけど、bufとBUFFER_SIZEの関連がこの行だけではわからないので、sizeof buf のほうが良いコード。
n = read()が読み込んだサイズ。途中でファイル終わることもあるので。これをwrite()にわたす
bufは初期化されず、常に上書きされている
bufを初期化しないのに、ループでread()から詰め込んでイケてるのはなぜだろう。もしかしてbufは常に上書きされてて、でもwrite()するときに必要な n byteしか出力しないからバレてないんじゃないだろうか。
試した。
こういうファイルを作る
$ cat test.txt
abc
d
catコマンドのbuffer sizeを変更
# define BUFFER_SIZE 4 <--- abc[改行]の4バイトがバッファになるように
write()を1byte (n+1)
多く出すようにする
if (write(STDOUT_FILENO, buf, n+1) < 0) die (path);
実行したら、通り過ぎたはずの c
が出てきた!
$ ./a.out test
abc
d
c$
1行目で入ったbufは きっと abc[改行]
2行目は d[改行]
がread()されたはずで、でもbufには前のループのが残ってるのできっと d[改行]c[改行]
になったんじゃないだろうか。n+1しか出してないので d[改行]c
だけ出た。
if (write(STDOUT_FILENO, buf, n+2) < 0) die (path);
↑ n+2 にしたら、予想通りcの後ろの改行もでました
$ ./a.out test
abc
d
c
$
perror()
よくわからんかった。errnoに合わせたエラーメッセージを標準出力に返す。stderrじゃないのね。
errnoがどこから来るのかよくわからん。die()すると、例えば read()のエラー EFAULT
が謎の通路を通ってperror()に届くんだろうか。perror()はprogram名(argv[0]) と errnoから作ったエラーメッセージを出す。
perror(path) といって渡したpathは何も表示されない。謎すぎる。
lseek()
fdのcursor(file offset)を動かせる。試しにwrite()するたびにlseek +1 してみた。
if (write(STDOUT_FILENO, buf, n) < 0) die (path);
lseek(fd, +1, SEEK_CUR); <------------------
各行の先頭文字を消すつもりだったけど、ずれがずれを生むのでずれずれになった。なるほど。
いくらでも引数をたしてもいいという ...
$ man ioctl
NAME
ioctl -- control device
SYNOPSIS
#include <sys/ioctl.h>
int
ioctl(int fildes, unsigned long request, ...);
第3引数の...
は可変長引数で、好きな数だけ好きな型の引数を渡していい、という意味
練習問題 5.8
catが引数無しで来たらstdinを引くようにする
引数なしエラーのとこで do_catに "/dev/stdin"
を渡すだけでよい。
main(int argc, char *arvg[])
{
int i;
if (argc < 2) {
do_cat("/dev/stdin");
}
結果
$ gcc -Wall -O2 cat.c && ./a.out test
abc
def
xyz
012
$ gcc -Wall -O2 cat.c && ./a.out
a
a
b
b
c
c
^C
最初シングルquoteで書いたらwarningが出て、double quotesにしたら直った。なんとなく最近読んだ これならわかる! C言語入門講座 第2版 が役に立った。何がどう違うのか忘れたけど、signel/doulbe quoteはすっごい違うものという記憶は残ってた。
char i = 'a'
— くじ@uturned (@uturned0) October 10, 2020
char i[] = "abc"
こんな謎のルールを学んだ。single double quote大事なのね
あと今更ながらポインタを使う意味がわかった、遠くの変数書き換えるためなのね
byte単位で読むread()の問題
きれいにいけば日本語も表示できるが、ちょっとでもずれると文字化けする。
文字単位で拾えればこうはならない。それは、次のchapterで。
ふう。やっとchapter 5 終わった。もう1時。おやすみなさい。本格的になってきた!