OSの2.xの課題

OSの講義の2.xの課題を解く

これが正解だぜ(どや)みたいなノリでは書いてないです、ただの平均的な技術力を持っているB2が解いているだけ

課題の取り組みをブログで書いてもいいという許可をもらった上で記事を書いてます

最新の課題内容であることや、答えが正しいかどうかは保証しません

このブログの情報で読者に何らかの不都合が生じても著者は責任は取らないです


2.1 Golangでプロジェクトを作るGitlLab編 (2022年度)

このページの問題を解く

Golangでプロジェクトを作る
GitlabのCI/CDを使う

Gitlabの設定に関しては11.xの記事で詳しく書いているので、スキップ!


Golangをinstall

  • $ brew install go

環境変数

  • zshrcに以下の設定を追記する
  • export GOPATH=$HOME/src/go
    
  • 書き終わったら$ source ~/.zshrcをしておきましょう

goを走らせる+gitlabに転送+testを書く

  • 以下のような構成のファイル・ディレクトリを用意する
.
├── .gitlab-ci.yml
├── fileWrite
│   ├── fileWrite.go
│   └── fileWrite_test.go
├── go.mod
└── main.go
  • main.go
package main

import (
    "fmt"
    "gitlab.ie.u-ryukyu.ac.jp/os/2022/e205723-fileWrite/fileWrite"
)

func main() {
    fmt.Println(fileWrite.Hello("Yoshisaur"))
}
  • fileWrite/fileWrite.go
package fileWrite

import "fmt"

// Hello returns a greeting for the named person.
func Hello(name string) string {
    // Return a greeting that embeds the name in a message.
    message := fmt.Sprintf("Hello, %v. Welcome!", name)
    return message
}
  • fileWrite/fileWrite_test.go
package fileWrite

import "testing"

func TestFileWrite(t *testing.T) {
    result := Hello("Yoshisaur")
    want := "Hi, Yoshisaur. Welcome!"
    if result != want {
        t.Errorf("fileWrite.Hello() = %q want %q", result, want)
    }
}
  • .gitlab-ci.yml
image: golang:latest

stages:
  - test
  - build

test:
  stage: test
  script:
    - go fmt $(go list ./... | grep -v /vendor/)
    - go vet $(go list ./... | grep -v /vendor/)
    - go test -race $(go list ./... | grep -v /vendor/)
  tags:
    - container

build:
  stage: build
  script:
    - mkdir -p mybinaries
    - go build -o mybinaries ./...
  artifacts:
    paths:
      - mybinaries
  tags:
    - container
  • `$ go mod init gitlab.ie.u-ryukyu.ac.jp/os/2022/e205723-fileWriteを実行してgo.modというファイルを生成する
  • 学科gitlabのos/2022というグループで「New project」→「Create blank project」→「e205723-fileWrite」という名前でCreate project
    • 公開設定はPublicにしておく
  • 作った学科GItLabのプロジェクトに先ほどのファイル群とgo.modをpushする
  • レポジトリのActionsというところでテストの結果がみれる
    • テストが失敗しているはず
  • fileWrite/fileWrite.goを以下のように編集する
package fileWrite

import "fmt"

// Hello returns a greeting for the named person.
func Hello(name string) string {
    // Return a greeting that embeds the name in a message.
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message
}
  • 変更したファイルをpushすると、テストが成功するようになる
  • 失敗・成功バージョンのテストのコンソールは以下のようになった

2.1 Golangでプロジェクトを作る GItHub編 (2021年度) (ACCEPT)


問題

このページの問題を解く

Golangでプロジェクトを作る
Gitlab(今回はGithubを使う)のCI/CDを使う

解く方針

  • 超軽量のチュートリアルこなすと楽かも
  • Gitlabじゃなく、Githubでもいいらしい
  • 色々アレンジしすぎてて原型留めてない、rejectされる可能性大になるけど頑張る
    • ダメだったらもう諦める、でもいい経験にはなるかな

Golangをinstall

  • $ brew install go

環境変数

  • zshrcに以下の設定を追記する
  • export GO111MODULE=on
    export GOROOT=/usr/local/opt/go/libexec
    export GOPATH=/Users/yoshisaur/src/go
    
  • 書き終わったら$ source ~/.zshrcをしておきましょう

goを走らせる+github(gitlab)に転送+testを書く

  • 以下のような構成のファイル・ディレクトリを用意する
    • .
      ├── .github
      │   └── workflows
      │       └── go.yml
      ├── fileWrite
      │   ├── fileWrite.go
      │   └── fileWrite_test.go
      └── main.go
      
    • main.go
      • package main
        
        import (
            "fmt"
            "github.com/e205723/fileWrite/fileWrite"
        )
        
        func main() {
            fmt.Println(fileWrite.Hello("Yoshisaur"))
        }
        
    • fileWrite/fileWrite.go
      • package fileWrite
        
        import "fmt"
        
        // Hello returns a greeting for the named person.
        func Hello(name string) string {
            // Return a greeting that embeds the name in a message.
            message := fmt.Sprintf("Hello, %v. Welcome!", name)
            return message
        }
        
    • fileWrite/fileWrite_test.go
      • package fileWrite
        
        import "testing"
        
        func TestFileWrite(t *testing.T) {
            result := Hello("Yoshisaur")
            want := "Hi, Yoshisaur. Welcome!"
            if result != want {
                t.Errorf("fileWrite.Hello() = %q want %q", result, want)
            }
        }
        
    • .github/workflows/go.yml
      • name: Go
        
        on:
          push:
            branches: 
              - main
        
        jobs:
          test:
            runs-on: ubuntu-latest
            steps:
              - name: Set up Go
                uses: actions/setup-go@v2
                with:
                  go-version: 1.17
        
              - name: Check out code
                uses: actions/checkout@v2
        
              - name: Test
                run: go test -v ./fileWrite
        
  • $ go mod init fileWriteを実行してgo.modというファイルを生成する
  • fileWriteという名前のレポジトリを作ってそこに先ほどのファイル群とgo.modをpushする
  • レポジトリのActionsというところでテストの結果がみれる
    • テストが失敗しているはず
  • fileWrite/fileWrite.goを以下のように編集する
    • package fileWrite
      
      import "fmt"
      
      // Hello returns a greeting for the named person.
      func Hello(name string) string {
          // Return a greeting that embeds the name in a message.
          message := fmt.Sprintf("Hi, %v. Welcome!", name)
          return message
      }
      
  • 変更したファイルをpushすると、テストが成功するようになる
  • 失敗・成功バージョンのテストのコンソールは以下のようになった

2.1の感想

  • 個人的にGolangとCI/CDどっちもやってみたかったので良かったという感じ
  • 授業ページが古いままだったからかなり自分流にアレンジして課題をこなしてしまった
    • ACCEPTになってくれ...(手を合わせている)
  • Golangのモジュールがアップロードされると、Go言語のモジュールをミラーリングするプロキシサーバにキャッシュが残ってしまってこの課題の手順の再現性を検証するのができなくなってしまった
    • なので先ほどの手順で完璧にこの結果を再現できるかはわからない、誰かこの記事を読みながらやった人は同じ結果になったか教えて欲しい

2.2 File 書き出し速度の測定


問題

このページの問題を解く

File への書き出し部分を作成する(buffered/ unbuffered)

Buffered かどうかでファイルへの書き出し速度は変わると予測される。
Buffered の影響を測定するのに、もっとも適切なファイルの大きさとbufferの大きさは、どの範囲にするのが良いか?
マインドマップを描いて考察する。

FileWrite に時間の測定をする部分を追加する。OS Xや Linux、SSD や HDD などで測定する。

実験データをグラフにする
PDF A4 1枚程度にまとめる。
LaTeX のソースと、PDFを repositoryにいれる。
書き出したデータファイル、中間ファイルは repository に中にあってはならない。

Buffer is 何?

  • Buffer(バッファ)とは、シンプルに書き込みを高速化する方法だと思って良い
  • Golangで回答を実装しているのでGolangのbuffered I/Oで考える
  • Golangのbufioは書き込み処理が行われるときに直接write(2)システムコールを呼び出しているのではなく、一旦ユーザー空間のメモリ領域に書き込む内容を保存して、保存している内容があるサイズを超えたらwrite(2)システムコールを呼ぶことになっている
  • 上記のbufioの仕組みの流れで、書き込む内容を保存する領域を「バッファ」と読んでいる、バッファはユーザー空間にある
  • しかし、なぜわざわざバッファに書き込む内容を溜め込んでからwrite(2)システムコールを呼ぶのか
    • これはユーザー空間のメモリ領域に書き込む速度とカーネル空間のページキャッシュに書き込む速度では、ユーザー空間のメモリ領域に書き込む速度が速いからである
  • 例がないとイメージしづらいので「0」(ASCIIなので1バイトの文字列)を4096回(=4KB)追記し続けるGolangのプログラムの実装パターンを考える
      1. バッファなしで書き込む、write(0x3, "0\0", 0x1)が4096回呼ばれる。
      1. 16バイトのバッファありで書き込む、write(0x3, "0000000000000000\0", 0x10)が256回呼ばれる
      2. 普通はバッファのサイズは4096バイトなのだが、システムコールの引数が非常に長くなって読みづらくなるのでバッファサイズを小さくしている
  • どちらの実装パターンが速いだろうか
  • ユーザー空間のメモリ領域に書き込む速度をx、 write(2)システムコールを呼び出してカーネル空間のページキャッシュに書き込む速度をyとすると
    • 実装パターン1の処理速度は4096yで、実装パターン2の処理速度は4096x + 256y
  • xはyに比べると無視できるほど小さいので(強引でごめんなさい)、実装パターン2の方が処理が速いということになる
  • 2.2の課題はバッファでどのくらいファイルの書き出しが速くなるかを調べる課題である、早速取り掛かっていく

File への書き出し部分を作成する

2.1の問題と同じレポジトリ(FileWrite)と同じレポジトリを使う。

e205723-filewrite/fileWrite/timeWrite.goというファイルを作成する。

課題を解き終わった時点でのファイル構成はこのようになった。

.
├── experiment
│   ├── csv
│   │   ├── time_results1.csv
│   │   └── time_results2.csv
│   ├── gnuplot
│   │   ├── 1.plt
│   │   ├── 2.plt
│   │   └── run.sh
│   ├── latex
│   │   └── 2_2.tex
│   ├── pdf
│   │   └── 2_2.pdf
│   ├── png
│   │   ├── graph1.png
│   │   ├── graph2.png
│   │   └── mindmap.png
│   ├── svg
│   │   ├── graph1.svg
│   │   ├── graph2.svg
│   │   └── mindmap.svg
│   └── txt
│       └── file.txt
├── fileWrite
│   ├── fileWrite.go
│   ├── fileWrite_test.go
│   └── timeWrite.go
├── go.mod
├── main.go
└── memo.txt

e205723-filewrite/fileWrite/timeWrite.goの中身は以下の通りである。

package fileWrite

import (
    "io"
    "os"
    "time"
    "fmt"
    "bufio"
)

func WriteSingleByte(writer io.Writer) {
    singleByteData := []byte("0")
    writer.Write(singleByteData)
}

func TimeWriteWithoutBuffer(fileByteSize int) int64 {
    filePath := "experiment/txt/file.txt"
    writer, err := os.Create(filePath)
    if err != nil {
        fmt.Println(err)
    }
    defer writer.Close()
    start := time.Now()
    for i := 0; i < fileByteSize; i++ {
        WriteSingleByte(writer)
    }
    executionTime := time.Since(start).Nanoseconds()
    return executionTime
}

func TimeWriteWithBuffer(fileByteSize int, bufferByteSize int) int64 {
    filePath := "experiment/txt/file.txt"
    writer, err := os.Create(filePath)
    if err != nil {
        fmt.Println(err)
    }
    defer writer.Close()
    bufferedWriter := bufio.NewWriterSize(writer, bufferByteSize)
    defer bufferedWriter.Flush()
    start := time.Now()
    for i := 0; i < fileByteSize; i++ {
        WriteSingleByte(bufferedWriter)
    }
    executionTime := time.Since(start).Nanoseconds()
    return executionTime
}

TimeWriteWithoutBuffer関数はfileByteSizeバイト分の「0」をバッファなしでファイルに書き込む関数である。「0」はASCIIなので1バイト、つまりfileByteSize回0が書き込まれるということになる。

TimeWriteWithBuffer関数はfileByteSizeバイト分の「0」をバッファありでファイルに書き込む関数である。


実験に関する考察

Buffered の影響を測定するのに、もっとも適切なファイルの大きさとbufferの大きさは、どの範囲にするのが良いか?

結論から言うと、bufferの大きさは4096バイト以上、ファイルの大きさはbufferよりも大きいサイズが適切である

Linuxファイルシステムのetx4はブロックサイズがデフォルトで4096バイトで、それ以下のサイズのバッファサイズで書き出しをしても無駄な切り上げが生じてしまう。ファイルの大きさはbufferのサイズより大きくするとwrite(2)システムコールが2回以上実行されるので、bufferの影響が見られる。

OS のファイルAPIにもとづいたMindMapは以下のmarkdownをもとに生成した。

# ファイルの書き出しに影響する要素とは
## 1. ファイルに書き出す流れ
### 1.1. プロセス
### 1.2. システムコールインターフェース
- 共通のシステムコールが呼び出される
### 1.3. VFSインターフェース
- 異なるファイルシステムを抽象化
  - ext4
  - FAT
  - isofs
  - NFS
### 1.4. ブロックデバイスインターフェース
- 異なるハードウェアを抽象化
  - ATA
  - Serial ATA
  - SCSI
  - Fibre channel
  - USB Mass Storage
## 2. ファイルの書き出しに影響するOSのファイルAPI
### 2.2. ファイルAPIの呼び出しの流れ(Linux v6.1)
  - 2.2.0. ソース
    - https://github.com/torvalds/linux/tree/v6.1
  - 2.2.1. write(2)
    - fs/read_write.c#L646
    - writeシステムコールがSYSCALL_DEFINEマクロで定義されている
    - 関数内でksys_write()が呼ばれている
  - 2.2.2. ksys_write()
    - fs/read_write.c#L626
    - writeシステムコールの実装がされている
    - 関数内でvsf_write()が呼ばれている
  - 2.2.3. vfs_write()
    - fs/read_write.c#L564
    - 各ファイルシステムが定義しているwrite操作を実行する
    - 関数内でnew_sync_write()が呼ばれている
  - 2.2.4. new_sync_write()
    - fs/read_write.c#L481
    - 関数内でwrite_iter=ext4_file_write_iter()が呼ばれている
  - 2.2.5. write_iter(ext4_file_write_iter)
    - fs/ext4/file.c#L686
    - 関数内でext4_buffered_write_iter()が呼ばれている
  - 2.2.6. ext4_buffered_write_iter()
    - fs/ext4/file.c#L270
    - 関数内でgeneric_perform_write()が呼ばれている
  - 2.2.7. generic_perform_write()
    - mm/filemap.c#L3716
    - buffered IOの実行がされている
    - 関数内でwrite_begin=ext4_write_begin()が呼ばれている=ext4のbuffered IOが開始される
### 2.3. ファイルの書き出しに影響する要素の考察
- ext4のブロックサイズは4096バイト
    - 出典: https://github.com/torvalds/linux/blob/master/Documentation/filesystems/fsverity.rst#ext4
    - バッファサイズは4096バイトのn倍(nは自然数)が適切

mind map


実験データの取得

main.goを以下のような内容に編集する

package main

import (
    "encoding/csv"
    "fmt"
    "os"
    "gitlab.ie.u-ryukyu.ac.jp/os/2022/e205723-fileWrite/fileWrite"
)

const (
    singleKiloBytes = 1024
)

var (
    fileSizeLimit1 = 8192
    bufferSizeArray1 = []int{1, 2, 4, 8, 16, 32, 64, 128, 256, 512}
    fileSizeLimit2 = 16384
    bufferSizeArray2 = []int{512, 1024, 2048, 4096, 8192, 16384}
)

func generateTimeResultRecords(fileSizeLimit int, bufferSizeArray []int) [][]string {
    // following 4 lines make [][]string slice
    timeResultRecords := make([][]string, fileSizeLimit)
    for i_fileSize := range timeResultRecords {
        timeResultRecords[i_fileSize] = make([]string, len(bufferSizeArray) + 1)
    }

    for i_fileSize := 0; i_fileSize * singleKiloBytes / 8 < fileSizeLimit; i_fileSize++ {
        fileSize := (i_fileSize + 1) * singleKiloBytes / 8
        timeResultRecords[i_fileSize][0] = fmt.Sprintf("%d", fileSize)
        for i_bufferSize, bufferSize := range bufferSizeArray {
            if bufferSize == 1 {
                // if bufferSize is 1 Byte, it can be considered writing without buffer
                timeResultRecords[i_fileSize][i_bufferSize + 1] = fmt.Sprintf("%d", fileWrite.TimeWriteWithoutBuffer(fileSize))
            } else {
                // if bufferSize is not 1 Byte, it can be considered writing with buffer
                timeResultRecords[i_fileSize][i_bufferSize + 1] = fmt.Sprintf("%d", fileWrite.TimeWriteWithBuffer(fileSize, bufferSize))
            }
        }
    }
    return timeResultRecords
}

func saveTimeRecordAsCsv(csvFilePath string, timeResultRecords [][]string) {
    // the result of experiment is saved in the file "time_results.csv"
    csvFile, _ := os.Create(csvFilePath)
    defer csvFile.Close()
    csvWriter := csv.NewWriter(csvFile)
    defer csvWriter.Flush()
    for _, record := range timeResultRecords {
        csvWriter.Write(record)
    }
}

func main() {
    timeResultRecords1 := generateTimeResultRecords(fileSizeLimit1, bufferSizeArray1)
    csvFilePath1 := "./experiment/csv/time_results1.csv"
    saveTimeRecordAsCsv(csvFilePath1, timeResultRecords1)

    timeResultRecords2 := generateTimeResultRecords(fileSizeLimit2, bufferSizeArray2)
    csvFilePath2 := "./experiment/csv/time_results2.csv"
    saveTimeRecordAsCsv(csvFilePath2, timeResultRecords2)
}

作業ディレクトリがe205723-fileWriteだと想定して作業をする

  • GitLabにGolangのコードを上げる
    • $ git add main.go fileWrite/timeWrite.go
    • $ git commit -m "feat: time write"
    • $ git push"
  • $ rsync -avPz ../e205723-filewrite amane:~
  • $ ssh amane
  • $ cd ~/e205723-fileWrite
  • $ go run .
  • $ exit
  • $ rsync -avPz amane:~/e205723-fileWrite/experiment/csv/ experiment/csv/

実験データをグラフにする

  • ./experiment/gnuplot/1.plt./experiment/gnuplot/2.plt./experiment/gnuplot/run.shというファイルを作成する
  • ここにあるファイルのように編集する
  • cd experiment/gnuplot
  • $ bash run.sh
  • 実験データをグラフ化できる
    • graph1
    • graph2

LaTeXでレポートを作成する

ここらへんは「マール描いて、フォイ」って感じで、できたレポートだけ貼っていく

実験レポート

関係ないけど、md-to-pdfを使えばマークダウンをそのままLaTeXにしてPDFにしてくれる


2.3 Mercurial での pull request


あれ、今ってMercurialって学科のやつ使えたっけ??

誰か他に2.2をやっている人を見つけなければならなそう


2.4 find / locate / Spotlight (ACCEPT)


問題

find で、これまで書いた c と java と python と go のソースコードをすべて調べて、それぞれの ファイル毎のword count を表示せよ。

wcの出力そのものを使う。大量にある場合(>300)は一部を表示する

    -w とかつけないこと!

300 以下ならそのまま結果をメールの本文に入れる
スクショを使ってはならない。

exec の ; の代わりに + を使うと速い。

mdfind を使って、同じことをやってみよ。

find

  • スクリプトは必ず~/workplaceというディレクトリに書いているのでworkplaceの中のファイルを調べるという方針でいい
  • $ find ~/workspace -type f \( -name "*.c" -o -name "*.py" -o -name "*.java" -o -name "*.go" \) -exec wc {} \+
    • 三者に見せられる範囲で得られた結果をまとめてみた
    •   28      73     465 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/3.x/3.1/c_env/3_1.c
        23      77     977 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/7.x/7.2/Program7_2.java
        20      55     652 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/7.x/7.3/Receiver2.java
        20      59     666 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/7.x/7.3/Source2.java
        22      60     784 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/7.x/7.3/Receiver.java
        20      59     665 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/7.x/7.3/Source.java
        73     182    2304 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/7.x/7.1/MouseRecorderB.java
        99     292    3417 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/7.x/7.1/MouseRecorderA.java
       211     530    5614 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/4.x/4.2/c_env/4_2.c
       294     993   11732 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/2.x/2.6/ls-laR/ls-laR.c
       838    2871   25853 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/2.x/2.6/ls-laR/build/CMakeFiles/3.24.1/CompilerIdC/CMakeCCompilerId.c
       838    2871   25853 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/2.x/2.6/ls-la/build/CMakeFiles/3.24.1/CompilerIdC/CMakeCCompilerId.c
      33097  105442 1072119 total
      

mdfindを使う

  • $ man mdfindでコマンドの使い方を見た
  • ついでにネット上でmdfindの使い方について調べた
  • $ mdfind -onlyin ~/workspace "kMDItemDisplayName == *.c || kMDItemDisplayName == *.java || kMDItemDisplayName == *.py || kMDItemDisplayName == *.go" | xargs wc | sort
    •  294     993   11732 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/2.x/2.6/ls-laR/ls-laR.c
       372    1295   12369 /Users/yoshisaur/workspace/enpit/bonne-coordination/backend/api/coordination.go
       412    1081   12761 /Users/yoshisaur/workspace/lectures/internet-architecture/internet-architecture-exercise/backend/api/functions.go
       444    1817   18694 /Users/yoshisaur/workspace/konochat/GPT2_finetuning/run_clm.py
       444    1817   18694 /Users/yoshisaur/workspace/lectures/software-development/GPT2_finetuning/run_clm.py
       589    1693   21647 /Users/yoshisaur/workspace/lectures/prog3/ShakeSphere/api/features/server.go
       608    1477   18085 /Users/yoshisaur/workspace/practice_isucon/private-isu/benchmarker/scenario.go
       780    2743   28208 /Users/yoshisaur/workspace/study/SimpleDB/SimpleDB_3.4/simpledb/jdbc/ResultSetAdapter.java
       838    2871   25853 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/2.x/2.6/ls-la/build/CMakeFiles/3.24.1/CompilerIdC/CMakeCCompilerId.c
       838    2871   25853 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/2.x/2.6/ls-laR/build/CMakeFiles/3.24.1/CompilerIdC/CMakeCCompilerId.c
       880    2249   20180 /Users/yoshisaur/workspace/practice_isucon/private-isu/webapp/golang/app.go
      1079    2330   32811 /Users/yoshisaur/workspace/isucon/isucon12-qualify/python/main.py
      1334    4353   64820 /Users/yoshisaur/workspace/isucon/isucon12-qualify/java/src/main/java/isucon12/Application.java
      1620    4926   46508 /Users/yoshisaur/workspace/isucon/isucon12-qualify/go/isuports.go
      33090  105369 1071071 total
      
    • めっちゃ速い

script言語で find を行う

自分の普段使いの script 言語で同じことをやってみよ。

  • Pythonを使って書いてみる
  • $ find.pyというスクリプトを作成して以下のように編集する
    • import argparse
      import re
      from pathlib import Path
      
      def find(path_to_execute_find):
          all_files = list(map(str, Path(path_to_execute_find).rglob('*.*')))
          regex_pattern = re.compile(r'.*\.(c|java|py|go)$')
          filtered_files = list(filter(regex_pattern.match, all_files))
      
          for file in filtered_files:
              print(file)
      
      if __name__ == '__main__':
          parser = argparse.ArgumentParser()
          parser.add_argument("path_to_execute_find")
          args = parser.parse_args()
          path_to_execute_find = args.path_to_execute_find
          find(path_to_execute_find)
      
  • $ python3 find.py <path>みたいに使う
  • $ python3 find.py ~/workspace | xargs wc | sortを実行したら以下のようになった
    •  294     993   11732 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/2.x/2.6/ls-laR/ls-laR.c
       372    1295   12369 /Users/yoshisaur/workspace/enpit/bonne-coordination/backend/api/coordination.go
       412    1081   12761 /Users/yoshisaur/workspace/lectures/internet-architecture/internet-architecture-exercise/backend/api/functions.go
       444    1817   18694 /Users/yoshisaur/workspace/konochat/GPT2_finetuning/run_clm.py
       444    1817   18694 /Users/yoshisaur/workspace/lectures/software-development/GPT2_finetuning/run_clm.py
       589    1693   21647 /Users/yoshisaur/workspace/lectures/prog3/ShakeSphere/api/features/server.go
       608    1477   18085 /Users/yoshisaur/workspace/practice_isucon/private-isu/benchmarker/scenario.go
       780    2743   28208 /Users/yoshisaur/workspace/study/SimpleDB/SimpleDB_3.4/simpledb/jdbc/ResultSetAdapter.java
       838    2871   25853 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/2.x/2.6/ls-la/build/CMakeFiles/3.24.1/CompilerIdC/CMakeCCompilerId.c
       838    2871   25853 /Users/yoshisaur/workspace/lectures/operating-system/uryukyu-lecture-OS/2.x/2.6/ls-laR/build/CMakeFiles/3.24.1/CompilerIdC/CMakeCCompilerId.c
       880    2249   20180 /Users/yoshisaur/workspace/practice_isucon/private-isu/webapp/golang/app.go
      1079    2330   32811 /Users/yoshisaur/workspace/isucon/isucon12-qualify/python/main.py
      1334    4353   64820 /Users/yoshisaur/workspace/isucon/isucon12-qualify/java/src/main/java/isucon12/Application.java
      1620    4926   46508 /Users/yoshisaur/workspace/isucon/isucon12-qualify/go/isuports.go
      33090  105369 1071071 total
      

option

locate command を使えるようにして、同じことをlocate を使って実現せよ。

  • $ sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist
    • ローカルPCのパスワードを入力する
  • $ cd /usr/libexec/
    • 後々実行するコマンドのエラー(retrieving current directory)を回避する
  • $ sudo /usr/libexec/locate.updatedb
    • ローカルPCのパスワードを入力する
  • 色々試したけど、自分のユーザディレクトリ以下のファイル・ディレクトリが読み込めない
    • 原因はこのstackoverflow風のサイトに書かれている
    • The locate database in built by the script /usr/libexec/locate.updatedb. The script is run by the user nobody in this line
      
  • nobodyっていうユーザがデータベースを立てているので、nobodyが扱えない権限のディレクトリ・ファイルはデータベースに登録されないみたい
    • $ ls -l /var/db/locate.database
      • -r--r--r--  1 nobody  wheel  12618853 Dec 31 07:42 /var/db/locate.database
        
    • $ ls -l ~/..の結果がdrwxr-x---+ 38 yoshisaur staff 1216 Dec 31 07:58 yoshisaur
      • nobody(=other)は僕のホームディレクトリを覗くことができないっていう解釈で合ってる??
  • ホームディレクトリの権限を変えればいいのかって感じになる
  • だけど、個人用PCとは言えど、なんか権限あたりよく知らないまま触るのよくなさそうなので、ここら辺でyak shaving終わり

2.4の感想

  • 最初は比較的得意分野じゃん!って飛びついてこの課題に取り掛かったけど、色々沼にハマった
  • mdfind速い、これから少しはこれ使おう
  • localも実行した感じ速い、linuxでも動くらしいのでlinuxディストリビューションのOSいじるときはそれを使ってみるのもアリかなって感じになった
  • 体感だけど、local = mdfind > findの順番で速い、ただ正規表現とかオプションの使い勝手はfindの方が良さそう
  • 色々掘りすぎて時間かけすぎたよん

2.5 zsh の使い方 (ACCEPT)


問題

このページの問題を解く

ssh で remote login した directory あるいは、terminal の複数の画面で最後にいた directory を覚えておくように zshrc に設定を付け加えて、正しく実行されることを確認せよ。

.zshrcに設定を付け加える

  • まず、このページのyomitanはグローバルなsshサーバという感じ、今はchatan、脳内変換して問題を解こうと思う
  • chatanにsshする
  • あ、chatanのホームディレクトリに.zshrcがすでに書かれている...
  • amaneでvm作るか
    • 以後の作業でトラブル(コンソールが起動しない、sshができない)が起きたら...
      • sshできない場合のトラブルシューティング
        • その1 UEFI shellを起動しないようにする
          • VMsshできないときは、amaneにssh($ ssh amane)して、以下のことをする。
          •  (amaneで実行)$ ie-virsh destroy internet-architecture-exercise-vm
             VMを停止する。
             (amaneで実行)$ ie-virsh edit internet-architecture-exercise-vm
             起動時にUEFI shellが立ち上がる設定があるかもしれないので、VMのxmlファイルを編集する。
             下記の行を探して削除して、保存して抜ける。
             <loader readonly='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE.fd</loader>
             (amaneで実行)$ ie-virsh destroy internet-architecture-exercise-vm
             (amaneで実行)$ ie-virsh start internet-architecture-exercise-vm
             これでsshできるようになるはず。
            
        • その2 enp0s3のnicをupしてdhcpする
          • (amaneで実行)$ ie-virsh console internet-architecture-exercise-vm
          • ユーザー名(ie-user)とパスワードを入力してログイン
          • /etc/netplan/99_config.yamlというファイルを作成して、以下のように編集
          • network:
            version: 2
            ethernets:
              enp0s3:
                addresses: [<akatsukiで取得したipv4 address>/20]
                gateway4: 10.0.15.254
                nameservers:
                  addresses: [10.100.10.20, 10.100.10.10]
                  search: []
                optional: true
            
          • $ sudo netplan applyを実行するとsshできるようになるし、Webサイトの配信も学内でできるようになる
  • amaneにssh
    • ホスト名はamane.ie.u-ryukyu.ac.jp
    • chatanを踏み台にする
    • Host amane
        HostName amane.ie.u-ryukyu.ac.jp
        User e2057xx
        Port xxxx
        ProxyJump chatan
      
  • $ ie-virsh define hoge -t Ubuntu-20
  • $ ie-virsh domiflist hoge
  • Akatsukiへgo、ipを申請する
  • $ ie-virsh start hoge
  • amaneからログアウト
  • $ ssh ie-user@<VMのIPアドレス>
    • パスワードはシス管に聞く
    • chatanを踏み台にしないとsshできないようになった
      • \~/.ssh/configに↓のような設定を書くといいかも
Host vm
  HostName <ipアドレス>
  User e2057xx
  ProxyJump chatan
  • ユーザを作って、sudoersに入れて、ie-userは消す(粒度粗くてす見ません)
  • ~/.ssh/config書く、鍵通す(ssh-copy-id、もうやったので粒度粗めに)、作ったvmssh
  • $ sudo apt install zsh
  • $ sudo chsh -s zsh
  • 一回ログアウト、再度ssh
  • .zshrcないぞって言われて色々な選択肢を提示されので作る選択肢を選ぶ
  • $ mkdir hogeから$ cd !$、そのままログアウト、再度ssh
  • $ pwd
  • この講義ページを頼りに.zshrcに書き込む内容を考える
  • 以下の記述を.zshrcに追加する
    • export dirfile="$HOME/.who.$host.$tty"
      export dirhfile="$HOME/.who.$host"
      
      if [[ ! -f $dirfile ]]; then
          if [[ ! -f $dirhfile ]]; then
              dir=$HOME
          else
              dir=`cat $dirhfile`
          fi
      else
          dir=`cat $dirfile`
          if [[ -d $dir ]]; then
              cd $dir
          else
              dir=$HOME
          fi
      fi
      
      function pushd {
          builtin pushd "$@"
          echo $PWD > $dirfile
          echo $PWD > $dirhfile
      }
      
      function popd {
          builtin popd "$@"
          echo $PWD > $dirfile
          echo $PWD > $dirhfile
      }
      
      function cd {
          builtin cd "$@"
          echo $PWD > $dirfile
          echo $PWD > $dirhfile
      }
      
  • $ source .zshrc
  • $ cd hogeからログアウト、再度ログインからの$ pwd
  • /home/<user>/hogeになっている
    • 成功

for文と**を組み合わせた例題を作成して、実行せよ、file commandを使用する

  • **ってなんだろう、file commandを指しているのかな
    • vm内に以下のような構成のディレクトリとファイルを生成した
      • .
        ├── hoge0
        │   ├── foo0
        │   └── foo1
        ├── hoge1
        │   ├── foo2
        │   ├── foo3
        │   └── foo4
        ├── hoge2
        │   └── foo5
        ├── hoge3
        │   ├── foo6
        │   ├── foo7
        │   ├── foo8
        │   └── foo9
        └── hoge4
            ├── foo10
            ├── foo11
            └── foo12
        
    • ディレクトリ内のファイルをfor文とfile commandを使用して調べる
    • 以下のようなスクリプトを用意した
      • example.sh
        • echo 'ssh to:'
          read server
          
          directories=($(ssh ${server} "ls ~"))
          
          for ((i=0;i<${#directories[@]};i++))
          do
          ssh ${server} "find ~/${directories[i]} -type f -exec file {} \;"
          done
          
    • 実行するとユーザ入力を促すので、sshしたいサーバのホスト名とかを打つといいと思う
    • $ echo vm | zsh example.sh
      • sh to:
        /home/yoshisaur/.who..: ASCII text
        /home/yoshisaur/.who.: ASCII text
        /home/yoshisaur/.ssh/authorized_keys: OpenSSH RSA public key
        /home/yoshisaur/hoge3/foo6: empty
        /home/yoshisaur/hoge3/foo7: empty
        /home/yoshisaur/hoge3/foo8: empty
        /home/yoshisaur/hoge3/foo9: empty
        /home/yoshisaur/.zcompdump: ASCII text
        /home/yoshisaur/hoge2/foo5: empty
        /home/yoshisaur/.sudo_as_admin_successful: empty
        /home/yoshisaur/hoge4/foo10: empty
        /home/yoshisaur/hoge4/foo12: empty
        /home/yoshisaur/hoge4/foo11: empty
        /home/yoshisaur/hoge1/foo4: empty
        /home/yoshisaur/hoge1/foo3: empty
        /home/yoshisaur/hoge1/foo2: empty
        /home/yoshisaur/hoge0/foo0: empty
        /home/yoshisaur/hoge0/foo1: empty
        /home/yoshisaur/.bashrc: ASCII text
        /home/yoshisaur/.zshrc: ASCII text
        /home/yoshisaur/.cache/motd.legal-displayed: empty
        /home/yoshisaur/.bash_history: data
        /home/yoshisaur/.bash_logout: ASCII text
        /home/yoshisaur/.zsh_history: ASCII text
        /home/yoshisaur/.profile: ASCII text
        /home/yoshisaur/hoge0/foo0: empty
        /home/yoshisaur/hoge0/foo1: empty
        /home/yoshisaur/hoge1/foo4: empty
        /home/yoshisaur/hoge1/foo3: empty
        /home/yoshisaur/hoge1/foo2: empty
        /home/yoshisaur/hoge2/foo5: empty
        /home/yoshisaur/hoge3/foo6: empty
        /home/yoshisaur/hoge3/foo7: empty
        /home/yoshisaur/hoge3/foo8: empty
        /home/yoshisaur/hoge3/foo9: empty
        

2.5の感想

  • シェルスクリプトを書きまくった...
    • 意外とこんなことできるんだっていう発見が多い
  • lsの結果から動的に配列を生成できるのは意外と便利だなって感じがある

2.6 Cのファイルシステムに関して調べる


問題はこのページにある

Cで

ls -la に相当するプログラムを書く

ls -laR に相当するプログラムを書く

DIR と struct stat はどこで定義されている?

FILE 構造体

debugger の操作

基本的な動作

ポインタの復習

ポインタの演算

dirp

Cでls -la に相当するプログラムを書く

$ ls -laを実行してみる

結果

total 0
drwxr-xr-x   4 yoshisaur  staff  128 Jun 22 03:41 .
drwxr-xr-x  10 yoshisaur  staff  320 Jan 31 00:07 ..
drwxr-xr-x   5 yoshisaur  staff  160 Jun 22 03:13 ls-la
drwxr-xr-x   5 yoshisaur  staff  160 Jun 22 04:41 ls-laR

これと同じ結果を出力するCのコードを記述する。

opendirとfstatを使うよう指示されているので、マニュアルをmanコマンドで調べる。

$ man 3 <hoge>でライブラリコール(C言語の関数など)のマニュアルを見ることができる。

補足: $ man 2 <hoge>システムコールのマニュアルを見ることができる。

$ man 3 opendirの実行結果で「SYNOPSIS」の項目から#include <dirent.h>を入力しなければならないことがわかる。 $ man 2 fstatの実行結果で「SYNOPSIS」の項目から#include <sys/stat.h>を入力しなければならないことがわかる。 このようにmanコマンドを駆使してls -laに相当するプログラムを実行する。

ls-la.cというls -laに相当するプログラムを作成した。

GitHub上のソースコードこちら

#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

void print_directory_contents(const char *dirpath, int print_dirname_total) {
    struct dirent *dp;
    DIR *dirp = opendir(dirpath);
    int total = 0;

    if (dirp == NULL) {
        perror("opendir failed");
        return;
    }

    while ((dp = readdir(dirp)) != NULL) {
        struct stat statbuf;
        char fullpath[1024];
        snprintf(fullpath, sizeof(fullpath), "%s/%s", dirpath, dp->d_name);
        if (lstat(fullpath, &statbuf) == -1) {
            perror("lstat failed");
            continue;
        }
        total += statbuf.st_blocks;
    }

    rewinddir(dirp);

    if (print_dirname_total) {
        printf("%s:\ntotal %d\n", dirpath, total);
    }

    while ((dp = readdir(dirp)) != NULL) {
        struct stat statbuf;
        char fullpath[1024];
        snprintf(fullpath, sizeof(fullpath), "%s/%s", dirpath, dp->d_name);
        if (lstat(fullpath, &statbuf) == -1) {
            perror("lstat failed");
            continue;
        }

        printf((S_ISDIR(statbuf.st_mode)) ? "d" : "-");
        printf((statbuf.st_mode & S_IRUSR) ? "r" : "-");
        printf((statbuf.st_mode & S_IWUSR) ? "w" : "-");
        printf((statbuf.st_mode & S_IXUSR) ? "x" : "-");
        printf((statbuf.st_mode & S_IRGRP) ? "r" : "-");
        printf((statbuf.st_mode & S_IWGRP) ? "w" : "-");
        printf((statbuf.st_mode & S_IXGRP) ? "x" : "-");
        printf((statbuf.st_mode & S_IROTH) ? "r" : "-");
        printf((statbuf.st_mode & S_IWOTH) ? "w" : "-");
        printf((statbuf.st_mode & S_IXOTH) ? "x" : "-");

        printf(" %3hu", statbuf.st_nlink);

        struct passwd *pw = getpwuid(statbuf.st_uid);
        struct group *gr = getgrgid(statbuf.st_gid);
        printf(" %-8s", pw ? pw->pw_name : "UNKNOWN");
        printf(" %-8s", gr ? gr->gr_name : "UNKNOWN");

        printf(" %9lld", statbuf.st_size);

        char buff[20];
        struct tm *timeinfo;
        timeinfo = localtime(&statbuf.st_mtime);
        strftime(buff, sizeof(buff), "%b %d %H:%M", timeinfo);
        printf(" %s", buff);

        printf(" %s\n", dp->d_name);
    }

    closedir(dirp);
}

int main(int argc, char *argv[]) {
    int print_dirname_total = argc > 2;
    if (argc == 1) {
        print_directory_contents(".", print_dirname_total);
    } else {
        for (int i = 1; i < argc; i++) {
            print_directory_contents(argv[i], print_dirname_total);
            if (print_dirname_total && i < argc - 1) {
                printf("\n");
            }
        }
    }
    return 0;
}

CMakeLists.txtというファイルを作成した。

GitHub上のソースコードこちら

cmake_minimum_required(VERSION 3.10)
project(ls_la)

set(CMAKE_C_STANDARD 99)
add_executable(ls_la main.c)

プログラムをcmakeで作成するときのcommand sequence + 実行結果

  • $ brew install cmake
  • $ mkdir build
  • $ cd build
  • $ cmake ..
  • $ make

$ ./ls_la . ..の実行結果

$ ./ls_la . ..
.:
total 128
drwxr-xr-x   7 yoshisaur staff          224 Jun 22 04:48 .
drwxr-xr-x   5 yoshisaur staff          160 Jun 22 03:13 ..
-rwxr-xr-x   1 yoshisaur staff        33816 Jun 22 04:48 ls_la
drwxr-xr-x  14 yoshisaur staff          448 Jun 22 04:48 CMakeFiles
-rw-r--r--   1 yoshisaur staff         5456 Jun 22 04:48 Makefile
-rw-r--r--   1 yoshisaur staff         1642 Jun 22 03:13 cmake_install.cmake
-rw-r--r--   1 yoshisaur staff        14658 Jun 22 04:48 CMakeCache.txt

..:
total 16
drwxr-xr-x   5 yoshisaur staff          160 Jun 22 03:13 .
drwxr-xr-x   4 yoshisaur staff          128 Jun 22 03:41 ..
-rw-r--r--   1 yoshisaur staff          108 Jun 22 04:43 CMakeLists.txt
-rw-r--r--   1 yoshisaur staff         2679 Jun 22 03:39 main.c
drwxr-xr-x   7 yoshisaur staff          224 Jun 22 04:48 build

$ ls -la . ..の実行結果

.:
total 128
drwxr-xr-x   7 yoshisaur  staff    224 Jun 22 04:48 .
drwxr-xr-x   5 yoshisaur  staff    160 Jun 22 03:13 ..
-rw-r--r--   1 yoshisaur  staff  14658 Jun 22 04:48 CMakeCache.txt
drwxr-xr-x  14 yoshisaur  staff    448 Jun 22 04:48 CMakeFiles
-rw-r--r--   1 yoshisaur  staff   5456 Jun 22 04:48 Makefile
-rw-r--r--   1 yoshisaur  staff   1642 Jun 22 03:13 cmake_install.cmake
-rwxr-xr-x   1 yoshisaur  staff  33816 Jun 22 04:48 ls_la

..:
total 16
drwxr-xr-x  5 yoshisaur  staff   160 Jun 22 03:13 .
drwxr-xr-x  4 yoshisaur  staff   128 Jun 22 03:41 ..
-rw-r--r--  1 yoshisaur  staff   108 Jun 22 04:43 CMakeLists.txt
drwxr-xr-x  7 yoshisaur  staff   224 Jun 22 04:48 build
-rw-r--r--  1 yoshisaur  staff  2679 Jun 22 03:39 main.c

totalの表示も出力したり、インデントを合わせるようにした。また、出力のソートは合わせるようには実装しなかった。


Cでls -laR に相当するプログラムを書く

$ ls -laRを実行してみる。

$ mkdir -p 1/2/3/4/5/6/7/8/9/A/B$ touch 1/2/3/4/5/6/7/8/9/A/B/hoge.txtを実行したあとに$ ls -laR 1を実行した。

結果

total 0
drwxr-xr-x  3 yoshisaur  staff   96 Jun 22 04:55 .
drwxr-xr-x  6 yoshisaur  staff  192 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff   96 Jun 22 04:55 2

1/2:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 3

1/2/3:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 4

1/2/3/4:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 5

1/2/3/4/5:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 6

1/2/3/4/5/6:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 7

1/2/3/4/5/6/7:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 8

1/2/3/4/5/6/7/8:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 9

1/2/3/4/5/6/7/8/9:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 A

1/2/3/4/5/6/7/8/9/A:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 B

1/2/3/4/5/6/7/8/9/A/B:
total 0
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 .
drwxr-xr-x  3 yoshisaur  staff  96 Jun 22 04:55 ..
-rw-r--r--  1 yoshisaur  staff   0 Jun 22 04:55 hoge.txt

これと同じ結果を出力するCのコードを記述する。

ls-laR.cというls -laRに相当するプログラムを作成した。

GitHub上のソースコードこちら

ビルドはls_laと同じようにbuildディレクトリを作成してその中でビルドした。

$ ./ls_laR ../1の実行結果

../1:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 2

../1/2:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 3

../1/2/3:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 4

../1/2/3/4:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 5

../1/2/3/4/5:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 6

../1/2/3/4/5/6:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 7

../1/2/3/4/5/6/7:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 8

../1/2/3/4/5/6/7/8:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 9

../1/2/3/4/5/6/7/8/9:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 A

../1/2/3/4/5/6/7/8/9/A:
total 0
drwxr-xr-x   3 yoshisaur staff           96 Jun 22 04:55 B

../1/2/3/4/5/6/7/8/9/A/B:
total 0
-rw-r--r--   1 yoshisaur staff            0 Jun 22 04:55 hoge.txt

11個目のBのディレクトリまで、再帰的にls -laを実行できているので、 *stackの格納数(初期の10個)が必要に応じて拡張するようにしていることが確認できた


DIR と struct stat はどこで定義されている?

$ clang -E ls-laR/main.cを実行

実行結果はかなり長いので割愛

実行結果をDIRで検索を書けた結果、以下のような実行結果の断片を発見した。

typedef struct {
 int __dd_fd;
 long __dd_loc;
 long __dd_size;
 char *__dd_buf;
 int __dd_len;
 long __dd_seek;
 __attribute__((__unused__)) long __padding;
 int __dd_flags;
 __darwin_pthread_mutex_t __dd_lock;
 struct _telldir *__dd_td;
} DIR;
# 105 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/dirent.h" 

$ less /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/dirent.h を実行し、DIRを探すと上記と同じDIRの定義を発見した。

$ clang -E ls-laR/main.cの実行結果からDIRと同様にstruct statで検索をすると以下のような実行結果の断片を発見した。

struct stat { dev_t st_dev; mode_t st_mode; nlink_t st_nlink; __darwin_ino64_t st_ino; uid_t st_uid; gi
d_t st_gid; dev_t st_rdev; struct timespec st_atimespec; struct timespec st_mtimespec; struct timespec
st_ctimespec; struct timespec st_birthtimespec; off_t st_size; blkcnt_t st_blocks; blksize_t st_blksize
; __uint32_t st_flags; __uint32_t st_gen; __int32_t st_lspare; __int64_t st_qspare[2]; };
# 221 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/stat.h"

$ less /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/stat.h を実行し、struct statを探すと上記と同じstruct statの定義を発見した。


FILE 構造体

$ clang -E ls-laR/main.cの実行結果からDIRと同様にFILEで検索をすると以下のような実行結果の断片を発見した。

struct __sFILEX;
# 126 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.h" 3 4
typedef struct __sFILE {
 unsigned char *_p;
 int _r;
 int _w;
 short _flags;
 short _file;
 struct __sbuf _bf;
 int _lbfsize;


 void *_cookie;
 int (* _Nullable _close)(void *);
 int (* _Nullable _read) (void *, char *, int);
 fpos_t (* _Nullable _seek) (void *, fpos_t, int);
 int (* _Nullable _write)(void *, const char *, int);


 struct __sbuf _ub;
 struct __sFILEX *_extra;
 int _ur;


 unsigned char _ubuf[3];
 unsigned char _nbuf[1];


 struct __sbuf _lb;


 int _blksize;
 fpos_t _offset;
} FILE;
# 65 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/stdio.h" 2 3 4

$ less /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/_stdio.hを実行して、FILEで検索をかけると以下のようなコードの断片を見つけた。

typedef struct __sFILE {
        unsigned char *_p;      /* current position in (some) buffer */
        int     _r;             /* read space left for getc() */
        int     _w;             /* write space left for putc() */
        short   _flags;         /* flags, below; this FILE is free if 0 */
        short   _file;          /* fileno, if Unix descriptor, else -1 */
        struct  __sbuf _bf;     /* the buffer (at least 1 byte, if !NULL) */
        int     _lbfsize;       /* 0 or -_bf._size, for inline putc */

        /* operations */
        void    *_cookie;       /* cookie passed to io functions */
        int     (* _Nullable _close)(void *);
        int     (* _Nullable _read) (void *, char *, int);
        fpos_t  (* _Nullable _seek) (void *, fpos_t, int);
        int     (* _Nullable _write)(void *, const char *, int);

        /* separate buffer for long sequences of ungetc() */
        struct  __sbuf _ub;     /* ungetc buffer */
        struct __sFILEX *_extra; /* additions to FILE to not break ABI */
        int     _ur;            /* saved _r when _r is counting ungetc data */

        /* tricks to meet minimum requirements even when malloc() fails */
        unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */
        unsigned char _nbuf[1]; /* guarantee a getc() buffer */

        /* separate buffer for fgetln() when line crosses buffer boundary */
        struct  __sbuf _lb;     /* buffer for fgetln() */

        /* Unix stdio files get aligned to block boundaries on fseek() */
        int     _blksize;       /* stat.st_blksize (may be != _bf._size) */
        fpos_t  _offset;        /* current lseek offset (see WARNING) */
} FILE;

これのshort fileがfile descriptorでstruct __sbuf bfがfile bufferである。


debugger の操作

以下を gdb ( on Linux) と lldb ( on Mac OS X) について、それぞれ調べよ。

まずは準備を始めよう

基本的な動作

    break point を設定して readdir しているところで 止める
    step 実行する
    continue する
    break point を解除する

gdb

ログ

lldb

ログ

ポインタの復習

stack の深さ3で止まるようにデバッガを設定する。(条件 break point )

自分で作ったstackのアドレスを表示する。

自分で作ったstackの中にあるDIRへのポインタを表示する。

自分で作ったstackの中にあるDIRの構造体を表示する。

gdb

ログ

lldb

ログ

ポインタの演算

stack に積まれているDIRをすべて表示してみる。
    配列を使って表示する       p stack[0]
    ポインタを使って表示する   p stack-1

の両方がある。
lldb/gdb で、演算している部分の命令を示せ。(演算が省略される場合もある) dissas などのコマンドを使う。

gdb

ログ

lldb

ログ

dirp

dirp は再帰的な表示の間で複数作られて、様々な値を取る。これを調べて表とグラフにせよ。
    表は LaTeX のコマンドを生成することにより作成する
    グラフは gnuplot を使う

どうして、このようなことになるのかを考察せよ。

main.cの一部をみる

  DIR *dirp = opendir(dirpath);
  if (dirp == NULL) {
    perror("opendir failed");
    return;
  }

opendir(dirpath)の結果が*dirpに代入された直後の24行目の if (dirp == NULL) {にブレイクポイントを設置する

結果的には様々な値が取られることはなかったのでグラフにはしないでログだけ残した

gdb

ログ

lldb

ログ


2.7 MTBF (ACCEPT)

問題はこのページにある

(1) 学科のSakuraのサーバは RAID1 構成になっている。容量は 40TB。使える容量は20TB。

40TBが 1TB の HDD x 40、 MTBF 100万時間を仮定して、1年間の故障台数を予測せよ。

Sakuaa は故障予測及び即日入れ換えのサービスが付いてる。(Downtime 1日) 2台目のHDDが壊れてデータが失われる確率はどれくらいか。

(2) 学科のSサーバは Ceph で構成されていて、多重度3になっている。容量は 80TB。半分の40TBをCephに使っている。

Ceph に付いて調べて簡単に説明し、RAID1 と比較せよ。

実際に使える容量は圧縮があるので自明にはわからない。しかし、使える容量を推定してみよ。(option)

Ceaph は多重度3なので二台目が壊れても大丈夫だが...

(3) 三重のバックアップが必要かどうかを議論せよ

(4) 学科のストレージ、学科のさくらクラウド、自分のノートPCを使うとして、現実的なバックアップ戦略を自分の視点から考察せよ。

(1) RAIDMTBFとsakura

  • Understanding Your Product Through Reliability Modeling を参考にして問題解く。
  • 40TBが 1TB の HDD x 40、 MTBF 100万時間を仮定して、1年間の故障台数を予測せよ。
    • MTBF = 1,000,000 hours
    • HDDの台数 = 40台
    • (24*365/1000000) * 40が答え
      • 0.3504台壊れる
      • 5年間同じHDDで学科システムを運用するとその5倍の台数(1.752台)は普通に壊れるので、RAID1でバックアップ戦略とらないと普通にデータ損失する
  • HDD故障時に入れ換えのDowntimeが1日で、RAID 1でバックアップを取っている場合、 2台目のHDDが壊れてデータが失われる確率はどれくらいか。
    • HDD故障の発生数がポアソン分布に従うと仮定し、故障率λをλ=1/MTBFとし、単位時間内にHDD1台が無事に稼働し続ける確率Rは以下のように求められる
      • R= e^(-λt)
    • 問題文から「λ = 1 / 1000000、t = 24」である
      • よって、1日でHDD1台が無事に稼働し続ける確率Rは、R= e^(-λt) = e^(-(1/1000000)*24)
    • 1 - R は「単位時間のうち、HDDが1台壊れる確率」である。
    • 「1日で40台あるHDDのうち1台が壊れる可能性」 = 「稼働台数」 * 「1日でHDDが1台壊れる確率」
      • (1 - R) * 40 (= (1 - (e^(-(1/1000000) * 24))) * 40)
    • 「40台のうち1台が壊れたとき、同じ日に39台あるうちのもう一台が壊れる確率」
      • (1 - R) * 39 (= (1 - (e^(-(1/1000000) * 24))) * 39)
    • 「2台同じ日に壊れたとき、2台ともRAID1の構成の中で同じデータを保存している確率」
      • 20 / (20^2)
    • 答えは「1日で40台あるHDDのうち1台が壊れる可能性」「40台のうち1台が壊れたとき、同じ日に39台あるうちのもう一台が壊れる確率」「2台同じ日に壊れたとき、2台ともRAID1の構成の中で同じデータを保存している確率」なので、
      • ((1 - (e^(-(1/1000000) * 24))) * 40) * ((1 - (e^(-(1/1000000) * 24))) * 39) * (20 / (20^2)) = 4.49269217e-80.000000045 = 0.0000045%

(2) Ceph

  • Cephを調べる
    • LouwrentiusというブログのCephに関する記事を参考にする
      • 初めて知った英語や用語
        • software-defined
          • (サーバやストレージ、ネットワークなどの物理的なITインフラが)ソフトウェアによって制御された
        • Commercial off-the-shelf(COTS)
          • 、既製品で販売やリースが可能となっているソフトウェア製品やハードウェア製品、または一般向けにライセンス提供されるものを採用すること
        • Host Bus Adapter (HBA)
          • コンピュータと他のネットワーク機器やストレージ機器を接続するハードウェア
        • JBOD(Just a Bunch of Disks)
          • RAIDを構成していないハードディスクの集まり
        • CRUSH
        • Erasure Coding
          • クラスタ上の有効容量を増加させる手法
          • データを複製する代わりに、パリティ情報を使用して、 ディスク障害が発生した場合にデータを再構築
        • The Object Storage Daemon (OSD)
          • ファイルを保存するディスクごとに起動されているデーモンプロセス
          • 対象となるディスクのOSDにファイルの読み書きを依頼する

  • Cephとは
    • 分散ストレージ機能を提供するソフトウェアのこと
    • 優れた拡張性と高い信頼性が特徴
  • Cephの構成要素
    • Ceph Monitor (ceph-mon)
      • モニターマップ、マネージャーマップ、OSDマップ、MDSマップ、CRUSHマップを含むクラスターの状態マップを保持する
      • Paxosというアルゴリズムで奇数個のモニタが用意される
    • Ceph Metadata Server (ceph-mds)
    • Ceph OSD (object storage daemon (ceph-osd))
  • 仕組み
    • Cephではマシンを「ノード」として扱う
    • ノードには複数のOSDがマシンの中のHDDの数分存在している
      • PaxosによってクラスタOSDの中から1つだけceph-monに置き換わることもある
    • Cephでは複数のノード(=マシン)を使ってデータを分散して保持している

CephとRAID1との比較

この記事を参考にした

CephのErasure Codingに着目して、CephとRAID1を比較していく。

CephとRAID1はどちらもHDDの故障によるデータの損失を防ぐ点では同じである。 CephのErasure Codingはデータを断片に分割したあとに、エンコードして、分散して冗長化することでデータの損失を防いでいる。RAID1はペアの2個目のHDDに1個目のHDDのデータをコピーしている。

故障したHDDのディスクを再度ビルドするときにかかる時間は、Erasure Codingを使うCephのほうが速い。

パフォーマンス面でCephのほうがRAID1より優れているといえる。


オプション: 使える容量を推定する

cephのドキュメントの「NO FREE DRIVE SPACE」を参照した

Cephにはfull ratio(デフォルト95%)とnear-full ratio(デフォルト 85%)という値が存在し、いずれかのOSDの使用率がfull ratioに達すると、受け付けを停止する。full-ratioの値を上げることができるが、容量が足りなくなってOSDが停止した場合、データを損失する可能性がある。OSDのfull ratioに達さないnear-full ratio以下の使用率に留めるのが懸命である。

80TBのうち40TBをCephに使っている。多重度3かつCephに使われている1つのHDDが1TBであると考えると、(40/3)(near-full ratio) = (40/3)0.85 = 11.333...

よって、使える容量は11.333...TBである


(3) 三重のバックアップの必要性

三重のバックアップのバックアップは必要である。

CephやRAID1の件で、適当な手段でバックアップを取ればデータの損失する確率は低い。しかし、それらの手段はバックアップの作成、リカバリ、保持などの段階、またはその手段においてのリスクを低くするわけではない。データのバックアップを取る上でHDDの故障以外のデータ損失のリスクを少なくするために、三重のバックアップを取る必要がある。

例を使用して説明をする。


京都大学のスーパーコンピュータシステムのLustreファイルシステムのファイル消失の重大障害

参考

日本ヒューレット・パッカード合同会社が開発したスパコン用ストレージをバックアップするプログラムの不具合により、京都大学のスーパーコンピュータシステムの大容量ストレージ(/LARGE0)の一部(約77TB)が意図せず削除された。

バックアップのスクリプトにはfindコマンドが使われていて、findコマンドの消去処理に渡す変数名を可読性を高めるために変更を加えたところ、bashの「シェルスクリプトの実行中に適時シェルスクリプトが読み込まれる挙動」によって副作用を起こして、未定義の変数を含むfindコマンドが実行してしまった。

これは推測の範囲ではあるがシェルスクリプトset -u(errors if an variable is referenced before being se)を追加していなかったのが原因であると思われる。なにより、この障害は、HDDの故障によって発生したものではない。ソフトウェアによる不備が原因となっている。


シス管: サーバ切り替え時のIPアドレスの変更によるCeph MonitorとOSDの接続の切断

参考: Seeker's eye Ceph 22 Sep 2020

サーバ切り替え時にIPアドレスが変わったが、Ceph Monitorの移行がされなくて、Ceph MonitorとOSDの接続が切れてOSDの中身が見れなくなった。この出来事もHDDの故障によって発生したわけではない。


(4) 学科のストレージ、学科のさくらクラウド、自分のノートPCを使った現実的なバックアップ戦略

自分の場合は、ラップトップPCのデータにのみしか大切なデータが存在しないので自分のラップトップPCのデータの損失を防ぐためのバックアッププランを考える

  • amaneでVMを2つ(AとB)用意する
    • A
      • 公開鍵認証方式によるログインのみを受け付ける
      • 秘密鍵以外のバックアップする必要があるファイルを保持させる
    • B
      • 公開鍵を登録せずにパスワード認証方式によるログインのみを受け付ける
      • パスフレーズを設定した秘密鍵(Permission: 400)のみを保持させる
        • des3で秘密鍵を暗号化する
        • $ openssl genrsa -out private.key -des3 2048秘密鍵を暗号化することができる
    • AとBに割り当てられるIPアドレスはAkatsukiにログインすればわかる
      • IPアドレスのデータを紛失することは学科のパスワードを忘れない限りない
    • amaneまたはAkatsukiが何らかの理由で運用ができなくなったときに備えて別のバックアップを用意する必要がある
  • 学科のさくらクラウド
    • umeのことだと思われるがアクセス権限がシステム管理チームにしかないはず
    • umeが運用しているmattermostにファイルをアップロードするなどがある
      • ただ、秘密鍵などの機密性が高い情報を含むファイルはアップロードできない
    • 学科のさくらクラウド使ったいいバックアップ戦略が思い浮かばないので、自分はSSDでファイル、暗号化した秘密鍵を保存するという戦略を取りたい
  • 自分のノートPC
    • Time Machineに対応したNASバイスにバックアップを作成する

2.7の感想

MTBFの計算やそれを使った確率の計算をするのは結構楽しかった。 Cephの概要を勉強するとき結構苦戦した。CephめんどくさそうだけどCRUSHやらPaxosやらオリジナリティがあって面白いアルゴリズムと出会えたのでよきかな。

学科のさくらクラウドを使ったいいバックアップ戦略が思いつかない。難しい。


2.8 df command (ACCEPT)

問題はこのページにある

df . を使うと、自分がどのファイルシステム上にいるかを調べることができる。

以下の場所がどこにあるかを df command を使って調べよ。

    Note PC 上の home direcotry
    urasoe 上の home direcotry
    kvm 上の disk image の置き場所

Note PC 上の home direcotry

  • $ cd ~
  • $ df .
Filesystem   512-blocks      Used  Available Capacity iused      ifree %iused  Mounted on
/dev/disk1s5 1953595632 220164864 1672872912    12% 1014435 8364364560    0%   /System/Volumes/Data

urasoe上のhome directory

urasoeにはsshできないのでchatanのhome directoryの場所を調べる

  • $ ssh chatan
  • $ cd ~
  • $ df .

セキュリティに悪そう?よくわからないが、詳しく書きすぎてもって感じなのでちょっと結果を加工させている

Filesystem                                                     1K-blocks       Used  Available Use% Mounted on
<サーバbのIPアドレス>:<ポート番号xxxx>,<サーバcのIPアドレス>:<ポート番号xxxx>,<サーバdのIPアドレス>:<ポート番号xxxx>:/ie-home/ 18432344064 9649704960 8782639104  53% /home
+chatan+e205723

\<ポート番号xxxx>はcephのポート番号かな、予想


kvm 上の disk image の置き場所

vmsshしてdfコマンドを実行してみる

  • $ ssh vm
  • $ cd ~
  • $ df .
Filesystem                  1K-blocks    Used Available Use% Mounted on
/dev/mapper/ubuntu--vg-root   8641944 2554012   5625904  32% /

kvm上のdisk imageは/dev/mapper/ubuntu--vg-rootに置かれている


2.8の感想

作業自体は簡単?少し自信ない


2.9 two factor authentication と password (ACCEPT)

問題はこのページにある

gitlab での two factor authentication
スマホに authenticator などの two factor authentication tool を入れて、学科のgitlabで two factor 認証を設定する
(IIJ Slim がおすすめか?)

gitlab の2020 の group に登録する

password 生成スクリプト
自分用の password 生成スクリプトを作り、出力を10個示せ。パスワードには十分な強度をもたせよ。

gitlab での two factor authentication

gitlab使えないのでgithubでtwo factor 認証を設定する、というか認証済みである


password 生成スクリプト

追記、以下の内容で自分はpassしたが、最新のセキュリティに準じた答えを考える必要がある

OS 9.xの講義資料では、「記号を入れずに15文字以上が推奨されてる(要出展)」と書いてある(自作のかわいい動物イラストをよくネットに登校する後輩くんによる情報提供。クレジットの許可取ってないけど、ご愛嬌ということで)

自分の回答は記号を含めたパスワードの生成をしてpassしているが、気をつけよう

以下のようなPythonスクリプトを書いた

import argparse
import secrets
import string

def generate_password(password_length):
    valid_chars_for_password = string.ascii_letters + string.digits + string.punctuation
    password = ''.join(secrets.choice(valid_chars_for_password) for _ in range(password_length))
    print(password)

if __name__=='__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("password_length")
    args = parser.parse_args()
    password_length = int(args.password_length)
    generate_password(password_length)

10回パスワードを生成するために書いたシェルスクリプト

#!/bin/bash
for i in {1..10}
do
   python3 generate_password.py 12
done

↓がシェルスクリプトを実行した結果

:XH?xD`$(Ijg
EhjcF|T!xO>i
Ya;Y~Z[FN,#0
j&C/r2~Zwv0'
6*{HJH~~:f~y
3X\cqR<irG{U
aW4P;6l9J\kO
K[=ts"F-|RBx
gC&3G60&Z7[r
bY[gatk`AMUW

↓がPasswordMonsterで出力したパスワードが強力であるかどうか調べた結果

回目 解析にかかる時間
1 10億年
2 100万年
3 450億年
4 8570億年
5 190億年
6 180億年
7 50億年
8 110億年
9 20億年
10 200万年

十分な強度のパスワードが生成できていることがわかる


2.9の感想

2段階認証大事だよね、救済コードとかも大事に保管しておこう パスワードは普段はLinux/UNIXのコマンドを使って生成している、名前とか誕生日は論外だね