書籍「ふつうのLinuxプログラミング」の学習記録
始めた理由
これに影響されて、Linuxの勉強をしようと思った。
明確な理由はない。フレームワークだけ使いこなせるエンジニアじゃなくてシステムの低いレイヤーの理解度がある、汎用性の高いエンジニアになりたいから勉強しようと思いました。
フロントエンドエンドエンジニアなら、本当は
umdeyとかで、TSやNext.jsで動きのあるサイト作るのがいいのかもしれませんが、僕はLinuxを勉強する。
参考書籍
Amazon.co.jp: ふつうのLinuxプログラミング 第2版 Linuxの仕組みから学べるgccプログラミングの王道 eBook : 青木 峰郎: 本
環境構築
下記のレポジトリに作成した。
下記のような感じで作成した。
Dockerfile
FROM ubuntu:latest
RUN mkdir /root/workspace
WORKDIR /root/workspace
RUN apt-get update -y --fix-missing \
&& apt-get install -y --no-install-recommends \
sudo \
build-essential \
git \
vim \
man \
manpages-dev \
strace \
gdb \
tree \
psmisc \
net-tools
- FROMは ubuntu最新版と指定
- RUNは コマンドの実行、実際にRUNの右側に書いてあるのは読んで字の如くですね
- WORKDIRは 作業ディレクトリ
apt-getコマンドとは
Debian系のディストリビューション(DebianやUbuntu)のパッケージ管理システムであるAPT(Advanced Package Tool)ライブラリを利用してパッケージを操作・管理するコマンドです。
apt-get update
はパッケージリストの更新です。
option
-y
インタラクティブ(ユーザーへの問い合わせ)に「yes」と答えます。
--fix-missing
ローカル環境のインデックスファイルが、サーバ上のインデックスファイルより古くても、無視して最後まで処理を続けるようです。
apt-get install
はパッケージのインストール/更新をしています。
デフォルトだと recommends しているだけの必須ではないパッケージも一緒に入って時間がかかるので --no-install-recommends をつけるのが常套手段
らしいので、つけた。
という感じで、sudo
やgit
などを追加しています。
docker-compose.yml
version: "3"
services:
ubuntu:
build:
context: .
dockerfile: ./Dockerfile
container_name: "ubuntu"
environment:
TZ: Japan
security_opt:
- seccomp:unconfined
volumes:
- ./workspace:/root/workspace
working_dir: "/root/workspace"
今回、コンテナは1つ作成(コンテナ名 ubuntu)。
こうすれば、
$ docker-compose build
これで、イメージがビルドされます。
ファイルを変更した際もまずビルドします。
security_opt
各コンテナに対するデフォルトのラベリング・スキーマ(labeling scheme)を上書きします。
volumes
volumes に名前をつけて定義された /workspace
という名前の volume を、コンテナ内のディレクトリ /root/workspace
にマウントしています。
working_dir
コンテナ内のワーキングディレクトリ。docker run コマンドの-w/--workdir に相当
docker-composeからDockerfileを実行するようにしている。
そうすると、レジストリからイメージ(ubuntu)を取得して、コンテナを作成。
コンテナの起動の仕方
次のコマンドは ubuntuを起動するためのコマンドです。
サービス内で bash としてコマンドを実行します。
$ docker-compose run ubuntu bash
run コマンドを使うと、サービスの設定ファイルで定義された通りに、同じ設定の新しいコンテナを開始します。
gccを使ったビルド
10pあたりを学習
コンテナ名ubuntu
を起動する
$ docker-compose run ubuntu bash
ホストOS上からエディタを使って
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("heloo!!!\n");
return 0;
}
ビルドします
$ gcc hello.c
gccはlinuxで一般的に用いられるコンパイラ
a.out
←ができている。gccに何もオプションをつけない場合、a.out
という名前で作成するらしい。
# gcc hello.c
# ls
a.out hello.c
実行してみる。
./
をつける必要があるらしい。
# ./a.out
heloo!!!
成功した。
C言語について
未経験なので、以下で勉強指定おく必要がありそう。
README追加した
14p
gcc -o hello hello.c
オプションo
で生成されるファイル名を指定できる。
16p
gcc -Wall
にある警告オプションをすべて有効にする。 デバッグ中は、これを使うといいらしい。
gcc -o
gcc -Oは、コンパイラの最適化レベルを設定します。
-O2をつけるのがいいらしい。
コマンドライン引数
コマンドライン引数とはmain 関数の引数のことです。
workspace/args.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i;
printf("argc=%d\n", argc);
for(i=0; i<argc; i++){
printf("argv=[%d]=%s\n", i, argv[i]);
}
exit(0);
}
$ gcc args.c -o args
コンパイル後、実行
./args x y z a c v
argc=7
argv=[0]=./args
argv=[1]=x
argv=[2]=y
argv=[3]=z
argv=[4]=a
argv=[5]=c
argv=[6]=v
コマンドライン引数とは x y z
のことです。
つまり、コマンドライン引数とはmain 関数の引数のことです。
実行するときに、 コマンドラインからコマンドライン引数に値を与えることができます。
main(int argc, char *argv[]){...}
main関数の引数として、受け取っていたやつですね。
- argcは渡された引数の数
- argc: "argument count"(引数の個数)の略
- argv
- argv: "argument vector"(引数の配列)の略
- 引数文字列の"配列へのポインタ"のことを指しているらしい
argv[0]は常に、プログラムが起動された時の名前が入っています。
なぜだろう?
コマンドライン引数はargv[1]から始まります。
まだ手探り。
明日は、第二章「Linuxカーネルの世界」をやっていく。
第二章「Linuxカーネルの世界」やってます。座学的な内容が多いから、ここに書くことない。
この書籍でいうところの重要概念である
- ファイルシステム
- プロセス
- ストリーム
を学んだ
第4章「linuxとユーザ」
ほぼ読むだけだった。
2022/03/14 - 2022/03/15
catコマンドを作る
一応、本に習ってLinuxコマンド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) {
// argv[0]はプログラムの名前
fprintf(stderr, "%s: file name not given\n", argv[0]);
exit(1);
}
// indexを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; // ファイルの読み込みが終わったら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 cat.c
:~/workspace# ls
a.out args args.c cat.c hello hello.c
ただなんのためにやっているか段々わからなくなってきた。まだ続ける。