LoginSignup
0
1

More than 1 year has passed since last update.

ふつうのLinuxプログラミングをやってみる その5

Last updated at Posted at 2020-11-04

有名な本らしいので買ってみました
ふつうの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");
}

https://webkaru.net/clang/function-declaration-prototypes/

人間が読める文字を格納する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);            <------------------

各行の先頭文字を消すつもりだったけど、ずれがずれを生むのでずれずれになった。なるほど。

image.png

いくらでも引数をたしてもいいという ...

$ 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はすっごい違うものという記憶は残ってた。

byte単位で読むread()の問題

きれいにいけば日本語も表示できるが、ちょっとでもずれると文字化けする。
文字単位で拾えればこうはならない。それは、次のchapterで。

image.png

ふう。やっとchapter 5 終わった。もう1時。おやすみなさい。本格的になってきた!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1