zebian.log

技術系備忘録とか

SerenityOSのProcFSではJSONを返す

  • SerenityOS

    • オープンソースで開発されているUNIX系OS
    • ユーザーインターフェースが90年代を彷彿させることで話題になっている(?)
    • 個人的にはUIがどうのこうのというよりも、コードがめちゃめちゃ読みやすいくて良いなと思っている
    • serenityos.org
  • ProcFS

    • UNIX系OSにおける疑似ファイルシステムで、プロセスに関する情報を出力する
    • cat /proc/[PID]/status など叩くとプロセス情報を吐いてくれる
    • あくまでインターフェースがファイルとなっているだけであり、ファイルそのものがあるわけではない
  • SerenityOSのProcFSを覗いてみる

    • Linuxと比べるとだいぶシンプルめになっている
    • 適当にcmdlineやfdsを読み出してみると、なんとJSONが返ってくる
    • ProcFSのmanより、The kernel can expose process related information in /proc. This functionality is used by various userland programs. All of the output layout (besides symbolic links) in the ProcFS nodes is JSON. とある
    • github.com
  • ソースコードを読む

    • さっきの/proc/[PID]/fdsを返す処理のコードを読んでみる
    • Kernel/FileSystem/ProcFS/ProcessExposed.cppの248行目procfs_get_fds_stats関数
    • auto array = TRY(JsonArraySerializer<>::try_create(builder));があり、JSONArraySerializerなるインスタンスが作られ、そのシリアライザーによってJSONが組み立てられていることがわかる
    • JSONArraySerializerの実装はAK/JsonArraySerializer.hにある
ErrorOr<void> Process::procfs_get_fds_stats(KBufferBuilder& builder) const
{
    auto array = TRY(JsonArraySerializer<>::try_create(builder));

    return fds().with_shared([&](auto& fds) -> ErrorOr<void> {
        if (fds.open_count() == 0) {
            TRY(array.finish());
            return {};
        }

        size_t count = 0;
        TRY(fds.try_enumerate([&](auto& file_description_metadata) -> ErrorOr<void> {
            if (!file_description_metadata.is_valid()) {
                count++;
                return {};
            }
            bool cloexec = file_description_metadata.flags() & FD_CLOEXEC;
            auto const* description = file_description_metadata.description();
            auto description_object = TRY(array.add_object());
            TRY(description_object.add("fd"sv, count));
            // TODO: Better OOM handling.
            auto pseudo_path_or_error = description->pseudo_path();
            TRY(description_object.add("absolute_path"sv, pseudo_path_or_error.is_error() ? "???"sv : pseudo_path_or_error.value()->view()));
            TRY(description_object.add("seekable"sv, description->file().is_seekable()));
            TRY(description_object.add("class"sv, description->file().class_name()));
            TRY(description_object.add("offset"sv, description->offset()));
            TRY(description_object.add("cloexec"sv, cloexec));
            TRY(description_object.add("blocking"sv, description->is_blocking()));
            TRY(description_object.add("can_read"sv, description->can_read()));
            TRY(description_object.add("can_write"sv, description->can_write()));
            Inode const* inode = description->inode();
            if (inode != nullptr) {
                auto inode_object = TRY(description_object.add_object("inode"sv));
                TRY(inode_object.add("fsid"sv, inode->fsid().value()));
                TRY(inode_object.add("index"sv, inode->index().value()));
                TRY(inode_object.finish());
            }
            TRY(description_object.finish());
            count++;
            return {};
        }));

        TRY(array.finish());
        return {};
    });
}
  • おまけ
    • プルリクではStreaming JSON serializersと呼ばれている
    • どうやら以前はHashMapを使ってJSON構造全体を組んでから返していたみたいだが、ヒープの枯渇問題がissue #484で報告されて、PR #489でストリーミング方式のシリアライザーが導入されたらしい
    • さっきのコードのコメントにあった// TODO: Better OOM handling.はここに繋がってくるのだろうか

2025年を振り返る

毎年恒例、今年を振り返るシリーズ。

↓去年 zebian.hatenablog.com

人生初LTをした

kernelvm.connpass.com speakerdeck.com

Kernel/VMで人生初のLTをやってきた。3分枠だったので話す内容を調整するのが大変だった。流石に暗記とかは無理なので現地ではカンペガン見でやらせてもらった。

あまり上手く話せた感じはしなかったけど、思ったよりもウケてたのでわざわざ現地まで来てやってよかったなと思った。前々からLTは学生のうちに経験しておきたいなと思っていたので今回挑戦してみたという感じになる。

16ビットCPUを自作した

今年の前半はTang Primer 20KというFPGAボードに自作16ビットCPUを実装するということをやっていた。

技術ブログを書くのをサボっていたので詳細な解説は特に作ってはいないが、簡単にまとめると

  • RISC-V風16ビット固定長(全32命令)
  • CPUの設計はChisel
  • 専用アセンブラをRustで書いた
  • MMIO(メモリマップドI/O)をFPGAに実装し、LEDやディスプレイをメモリの読み書きで制御できる

といった感じで、割と本格的に作り込んだつもりではある。

最終的に簡単なブロック崩しゲームをアセンブラで実装したものを学校行事やNT東京などで展示した。こちらもたくさんの方に見に来ていただけたので出展してよかった。

コードはすべてGitHubに公開しているのでよかったらどうぞ。

その他

今年は気分転換でよく映画館に映画を観に行ったような気がする。AIで遊んだり絵を描いたりはいつものこととして、あとは12月に入ってから本をよく読むようになった。

学生生活最後の1年

就活が爆速で終わったため、今年1年間楽に過ごせるかと思ったら全くそんなことはなかった。

授業選択はあるとはいえ専門学校なので必修が多く、基本朝から夕方まで学校だし、会社事務所でシステムエンジニアのバイトをしており、仕事の方もまあまあ重たい内容をやっていたりして、、、

かといって稼げているかと言われたら全くそうでもなく、むしろ車通学のためガソリン代が馬鹿にならないし、今年は東京での出展なども多く、交通費とホテル代で大赤字だった。

一応良いこともあり、学校内でこんな低レイヤー変人活動をしているのはほぼ自分だけだったため、展示企画を任せてもらえたり、後輩に技術の面白さを伝えられたことは良かったかなと思う。良かったと思いたい。

あとはあまりにも忙しすぎて体調やメンタルを崩すこともよくあったため、今年はあえて頑張らないようにすることを目標に過ごしてたが、それも意外と難しかった。学生にしては本当によくやってると思う。来年こそゆるく生きたい。

まとめ

  • 今年はLTや出展などでリアルイベントの機会が多かった
  • とにかく時間と金が足りなかった
  • 来年から社会人になります

自作OS向けlibcとそのRustバインディングを書いた話

この記事は自作OS Advent Calendar 2025 25日目の記事です。

前回のアドベントカレンダーの記事はこちら。自作OSにDOOMを移植した話です。 zebian.hatenablog.com

また、開発している自作OSのリポジトリはこちらです。 github.com

libcを自作する

DOOMの移植に利用したdoomgenericはC言語で書かれているため、stdio.hstring.hなどにある関数を利用するためには、当然ながらlibcを自作OS環境向けに実装する必要がありました。

newlibやmuslを使っても良かったのですが、自作OSのシステムコールのインターフェースが独自のものであるということと、現状すべてのlibc関数が必要というわけではなかったため、今回はスクラッチで実装しました。

コード例

apps/libc/syscalls.h

#define SN_READ 0
#define SN_WRITE 1
// ...

extern int sys_read(int fd, void* buf, size_t buf_len);
extern int sys_write(int fd, const void* buf, size_t buf_len);

// ...

apps/libc/stdio.c

// ...

int fprintf(FILE *stream, const char *fmt, ...) {
    printf("[DEBUG]fprintf called\n");
    return -1;
}
// ...

int puts(const char *c) {
    int ret = sys_write(FDN_STDOUT, c, strlen(c));

    if (ret == -1)
        return -1;

    ret = sys_write(FDN_STDOUT, "\n", 1);
    if (ret == -1)
        return -1;

    return 0;
}
// ...

FILE *fopen(const char *filepath, const char *mode) {
    // ...

    int fd = sys_open(filepath, flags);
    if (fd == -1)
        return NULL;
    // ...

    FILE *file = (FILE *)malloc(sizeof(FILE));
    file->fd = fd;
    file->buf = NULL;
    file->stat = stat;
    file->pos = 0;
    return file;
}

// ...

libcの関数はとにかくたくさんあるため、ひたすら必要な関数を実装するほかありません。特にprintf関数の実装の難易度がかなり高めということだけ言っておきます。可変長引数のパース、フォーマット文字列を受け取って指定通りの文字列を組み立てる、数値変換などなど、、、

正直、関数の内部実装まで既存のlibcを真似る必要はありませんが、Linuxなど既存プラットフォームで動作するCのコードを自作OS上で動かす(移植する)場合、関数の引数や戻り値、ヘッダファイルのディレクトリ構成は揃えておいたほうが移植によるコードの変更を最小限に抑えることができます。

libcとしてコンパイルするためのオプション

SRC_FILES := stdio.c stdlib.c string.c syscalls.c printf.c window.c ctype.c sys/stat.c
OBJ_FILES := $(SRC_FILES:.c=.o)
LIB_FILE := libc.a

CFLAGS := -Werror -g -m64 -nostdlib -fno-builtin -fno-stack-protector -std=c11

# ...

libcとしてコンパイルするために必要なコンパイルオプション(gcc)は以下のとおりです。

  • -nostdlib: 標準ライブラリをリンクしない
  • -fno-builtin: 関数呼び出しの最適化を無効化
  • -fno-stack-protector: スタック保護機能を無効化

-fno-stack-protectorに関しては必須ではありませんが、このオプションを使わない場合、__stack_chk_fail__stack_chk_guardなどのスタック保護に関する関数を自前で実装する必要があります。

インクルードガード(#ifndef/#define

Cでは同じヘッダファイルをいくつもインクルードすると定義が重複してしまい、コンパイルエラーになります。それを防ぐためにインクルードガードというものがあります。使い方は単純で、#ifndef#endifで定義を囲み、中にインクルードガード用の定数を定義するだけです。普段あまりCを書かないので今回初めてインクルードガードを書きました。めっちゃ便利。

#ifndef _STDIO_H
#define _STDIO_H

#include <stdarg.h>
extern int printf(const char* fmt, ...);

#endif

main関数

Cのmain関数は通常int main(int argc, char const* argv)ですが、-nostdlibコンパイルするとmain関数を書いても自動的に呼び出されません。そこでlibc側でエントリポイント_startを実装し、main関数を呼び出す処理を書くことで、擬似的にmain関数から開始することができます。

apps/libc/main.c

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-function-declaration"

#include "stdio.h"

void _start(int argc, char const* argv[]) {
    exit((uint64_t)main(argc, argv));
}

#pragma GCC diagnostic pop

関数を呼び出す前にプロトタイプ宣言が必要になりますが、通常、main関数は引数や戻り値をvoidにするなど、柔軟な書き方をすることができます。main関数のプロトタイプ宣言をしてしまうと、宣言したその特定の型としてしか受け付けることができなくなってしまうため、ここではあえてプロトタイプ宣言をせず、#pragmaコンパイル時の警告を抑制し、リンカによる名前解決のみに頼ります。

プロトタイプ宣言がないため、コンパイラによる型チェックはされませんが、渡される引数はint argcchar const* argv[]であるため、それ以外の型で引数を受け取ろうとすると、voidは大丈夫ですが、実行時にスタックやレジスタが壊れてクラッシュするかもしれません。

ちなみに引数をスタック上に用意するのはカーネルの仕事であるため、カーネル側のコードを変更することで型を変えることもできます。

Rustバインディングを作る

bindgen

カーネルをRustで書いているにも関わらずlibcつまりはC言語を書いているため、システムコールといった橋渡しの部分で使われる専用の構造体や定数をlibc側とカーネル側で2重で書かなければいけません。

最初のうちはそれでもいいと思いますが、libcの構造体が増えるにつれてRustで再定義しなければいけない数も増えますし、構造体のメンバーの一部を変更、定数を変更するといった場合に二度手間になります。

そこでCのヘッダファイルからRustのバインディングコードを自動生成してくれるbindgenというクレートを使います。

#define IOMSG_CMD_REMOVE_COMPONENT 0x80000000
#define IOMSG_CMD_CREATE_COMPONENT_WINDOW 0x80000001
#define IOMSG_CMD_CREATE_COMPONENT_IMAGE 0x80000002

typedef struct {
    uint32_t cmd_id;
    uint32_t payload_size;
} iomsg_header;

ヘッダファイルに書いた定数や構造体から

pub const IOMSG_CMD_REMOVE_COMPONENT: u32 = 2147483648;
pub const IOMSG_CMD_CREATE_COMPONENT_WINDOW: u32 = 2147483649;
pub const IOMSG_CMD_CREATE_COMPONENT_IMAGE: u32 = 2147483650;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct iomsg_header {
    pub cmd_id: u32,
    pub payload_size: u32,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
    ["Size of iomsg_header"][::core::mem::size_of::<iomsg_header>() - 8usize];
    ["Alignment of iomsg_header"][::core::mem::align_of::<iomsg_header>() - 4usize];
    ["Offset of field: iomsg_header::cmd_id"]
        [::core::mem::offset_of!(iomsg_header, cmd_id) - 0usize];
    ["Offset of field: iomsg_header::payload_size"]
        [::core::mem::offset_of!(iomsg_header, payload_size) - 4usize];
};

自動でこのようなRustコードを生成してくれます。

ライブラリとして新しくRustプロジェクトlibc-rsを作り、バインディングコードの生成を指示するbuild.rsを書きます。

libc-rsは別のRustプロジェクトの依存に追加することで参照することができます。

apps/libc-rs/build.rs

fn main() {
    // Cargo.tomlのfeaturesで指定されたフラグを確認
    let is_for_kernel = std::env::var("CARGO_FEATURE_KERNEL").is_ok();
    if !is_for_kernel {
        println!("cargo:rustc-link-lib=static=c_with_main");
    }

    // ヘッダファイルを登録
    let mut builder = bindgen::Builder::default();
    for header in headers {
        builder = builder.header(header.to_str().unwrap());
    }

    let bindings = builder
        .use_core() // stdではなくcoreクレートを使用する型を生成
        .generate()
        .expect("Failed to generate bindings");
    // bindings.rsを生成する
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Failed to write bindings");
}

apps/libc-rs/src/lib.rs

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

kernel/Cargo.toml

[package]
name = "kernel"
version = "0.1.0"
edition = "2021"
authors = ["Zakki <zakki0925224@gmail.com>"]
description = "A hobby operating system written in Rust."

[dependencies]
common = { path = "../common" }
libc-rs = { path = "../apps/libc-rs", features = ["kernel"] }
pci-ids = "0.2.5"

libc-rsとRust製ユーザーアプリ

libc-rskernelから参照することで、libcで定義した構造体の型定義がカーネルから使えるようになりますが、libc-rsを参照してRustでユーザーアプリを作ることもできます。Rustでユーザーアプリを作る場合はno_std(標準ライブラリを使わない)環境にしないといけませんが、その場合VecStringといった動的メモリ確保が必要な機能や、パニック時に必要なハンドラが利用できなくなります。

そのため、no_std環境では動的メモリ確保を行うためのglobal_allocatorとパニックハンドラpanic_handlerを実装する必要があります(ちなみにno_std環境でコンパイルするためにはpanic_handlerの実装は必須です)。

しかし、Rustでユーザーアプリを作るたびに毎回実装しなければならないとなると非常に面倒くさいので、libc-rs側で実装してしまえという話です。

global_allocatorpanic_handlerについての詳細な解説はWriting an OS in Rustという記事が参考になります。 os.phil-opp.com os.phil-opp.com

apps/libc-rs/src/lib.rs

#![no_std]

#[cfg(not(feature = "kernel"))]
use linked_list_allocator::LockedHeap;

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

// heap
#[cfg(not(feature = "kernel"))]
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();

#[cfg(not(feature = "kernel"))]
#[doc(hidden)]
pub fn _init_heap() {
    let heap_size = 1024 * 1024;
    let heap = unsafe { malloc(heap_size as u64) as *mut u8 };
    unsafe {
        ALLOCATOR.lock().init(heap, heap_size);
    }
}

// panic
#[cfg(not(feature = "kernel"))]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    println!("{:?}", info.message());
    println!("{:?}", info.location());

    unsafe {
        exit(-1);
    }
}

これら以外にも、引数を受け取る処理やprint!/println!マクロの定義など、no_std環境では様々な手続きが必要であるため、それらもまとめて実装してしまいます。

// parse args macro
#[cfg(not(feature = "kernel"))]
#[doc(hidden)]
pub unsafe fn _parse_args(argc: usize, argv: *const *const u8) -> Vec<&'static str> {
    let mut args = Vec::new();
    for i in 0..argc {
        let ptr = *argv.add(i);
        let mut len = 0;
        while *ptr.add(len) != 0 {
            len += 1;
        }

        let slice = core::slice::from_raw_parts(ptr, len);
        let s = match str::from_utf8(slice) {
            Ok(s) => s,
            Err(_) => "",
        };
        args.push(s);
    }

    args
}

#[cfg(not(feature = "kernel"))]
#[macro_export]
macro_rules! parse_args {
    () => {{
        use core::arch::asm;

        let argc: usize;
        let argv: *const *const u8;
        unsafe {
            asm!("mov {}, rdi", out(reg) argc, options(nomem, nostack));
            asm!("mov {}, rsi", out(reg) argv, options(nomem, nostack));
        }

        $crate::_init_heap();
        let args = unsafe { $crate::_parse_args(argc, argv) };
        args
    }};
}

// print macros
#[cfg(not(feature = "kernel"))]
struct Writer;

#[cfg(not(feature = "kernel"))]
impl fmt::Write for Writer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        unsafe {
            printf(format!("{}\0", s).as_ptr() as *const _);
        }

        Ok(())
    }
}

#[cfg(not(feature = "kernel"))]
#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
    Writer.write_fmt(args).unwrap();
}

#[cfg(not(feature = "kernel"))]
#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => ($crate::_print(format_args!($($arg)*)));
}

#[cfg(not(feature = "kernel"))]
#[macro_export]
macro_rules! println {
    () => ($crate::print!("\n"));
    ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}

libc-rs側で複雑な手続きを実装することによって、Rustでユーザーアプリを書く際に実装の手間が大幅に減ります。

libc-rsからシステムコールを呼び、embedded-graphicsクレートを使ってライフゲームを作りました。

apps/lifegame/src/main.rs

#![no_std]
#![no_main]

use embedded_graphics::{pixelcolor::Rgb888, prelude::*, primitives::*};
use libc_rs::*;

// ...

#[no_mangle]
pub unsafe fn _start() {
    let _args = parse_args!();

    let title = "lifegame\0";
    let cdesc_window = create_component_window(
        title.as_ptr() as *const _,
        100,
        100,
        WIDTH + 10,
        HEIGHT + 50,
    );
    if cdesc_window.is_null() {
        println!("Failed to create component window");
        exit(-1);
    }

    let fb = malloc((WIDTH * HEIGHT * 4) as u64);
    if fb.is_null() {
        println!("Failed to allocate framebuffer memory");
        exit(-1);
    }

    let cdesc_image =
        create_component_image(cdesc_window, WIDTH, HEIGHT, PIXEL_FORMAT_BGRA as u8, fb);
    if cdesc_image.is_null() {
        println!("Failed to create component image");
        exit(-1);
    }

    let mut eg_fb = Framebuffer {
        fb: fb as *mut u8,
        width: WIDTH,
        height: HEIGHT,
    };

    initialize_board();
    draw_board(&mut eg_fb, 0);

    loop {
        let start_time = sys_uptime();
        while sys_uptime() - start_time < DELAY_MS {
            // wait
        }

        unsafe {
            GENERATION += 1;
        }
        compute_next_generation();
        unsafe {
            draw_board(&mut eg_fb, GENERATION);
        }
    }
}

featureで使い分ける

Rustにはfeatureという機能があり、特定の機能の有効/無効を切り替えるフラグを付けることができます。 doc.rust-lang.org

libc-rsはRust製ユーザーアプリでもカーネルでも参照されることになるのですが、global_allocatorpanic_handlerは1プロジェクトにつき1つしか実装することができないという決まりがあります。もちろんカーネル側でも実装しているため競合することになるのですが、そこでfeatureを活用し、フラグ制御で実装を消します。

kernel/Cargo.toml

[package]
name = "kernel"
version = "0.1.0"
edition = "2021"
authors = ["Zakki <zakki0925224@gmail.com>"]
description = "A hobby operating system written in Rust."

[dependencies]
common = { path = "../common" }
libc-rs = { path = "../apps/libc-rs", features = ["kernel"] }
pci-ids = "0.2.5"

カーネルCargo.tomllibc-rskernelフラグを指定します。

apps/libc-rs/src/lib.rs

// heap
#[cfg(not(feature = "kernel"))]
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();

#[cfg(not(feature = "kernel"))]
#[doc(hidden)]
pub fn _init_heap() {
    let heap_size = 1024 * 1024;
    let heap = unsafe { malloc(heap_size as u64) as *mut u8 };
    unsafe {
        ALLOCATOR.lock().init(heap, heap_size);
    }
}

// ...

#[cfg(not(feature = "kernel"))]つまり「kernelフラグが立っているときは実装しない」ということになります。C言語で言う条件付きコンパイルのようなものです。

まとめ

  • libcを自作した。必要な関数だけをつまんで実装していくほうが良いと思う。
  • bindgenを使ってlibcのRustバインディングを作った。
  • no_std Rustの面倒くさい部分はバインディングライブラリで隠してしまおう。

Github CopilotではLSPがあなたにプロトコル実装を強制する

あえてクオリティを落とす勇気

完璧主義な自分が最近意識していることをまとめる。

正直技術系以外のポエミーな話題を書こうかかなり長い時間迷っていたが、最近ブログのネタが無くてほとんど更新できてないということと、案外自分のブログはあとから見返すことが多いので自戒を込めて書こうと思った。

完璧主義と高いクオリティ

学校の課題でも仕事でも趣味でも、持ち前の完璧主義という性格のせいで割と何でも無意識に高いクオリティを目指そうとしてしまう、ということが長い間続いてきた。

普通に考えると、高いクオリティを目指すのは良いことではある。常にクオリティを上げるために自己研鑽できるし、周囲から良い評価をもらえて一目置かれるし、そのおかげで美味しい話にこぎつけたりもする。

しかし、我々は人間であり機械ではないため、常にそんなことをしていると普通に疲労とストレスが溜まる。そして抜けられない負のスパイラルへ、、、

  • 完璧主義という性格特性上、手を抜くのが許せない
  • 周りが褒めてくれるため自己承認欲求が高まる
  • 失敗するのが怖い(というよりも失敗によって周りから馬鹿にされることに対する恐怖)

クオリティが高いことだけが良いことではない

最近趣味で絵を描いていて気づいたこと。何時間もかけて丁寧に仕上げた絵よりも、数分で適当に描いたラフのほうが上手く見えるということがよくある。上手い下手は一旦置いておいて、高いクオリティを目指そうとするあまり、気合いが入りすぎて視野が狭くなっており、全体を俯瞰して見ることができていなかったということだと思う。

これは絵描き以外のことでも当てはまると思っていて、ある特定の領域だけ飛び抜けてクオリティが高いところで全体との辻褄が合わなくなるし、全体のクオリティがものすごく高かったとしても、例えばそれが課題や仕事だった場合、そもそも求められていることと頓珍漢な方向に伸びていたら意味がない

評価基準をカンストすることは決して無駄ではないが、価値に見合わない

課題(ここでは数学の問題集のような正解/不正解のブーリアン回答ではなく、作文や工作、研究課題などの評価基準が明確でないものをいう)や仕事でも、自分自身が求めるクオリティが、相手が求めるクオリティの何十倍も先を行っていたということは結構あったりする。

結局どれだけすごいことをこなしたとしても、相手の評価基準を超えたものは「すごい」としか認識できないため、その評価基準を上手いこと把握して、それを少し上回る程度で頑張ればいいのではないだろうか。

いや、むしろ頑張らなくていいのではと最近は思う。常に高得点を維持したところで成績がさほど重要でないなら意味がないし、仕事であればその働きに見合う給料を貰わないとただのボランティアである。究極のところ自己満足との戦いであり、そんな日々のつまらない課題や仕事で貴重なエネルギーや時間を消費するのはもったいない。完璧主義の使いどころを履き違えている、と考えるが正しいのかもしれない。

個人的AI使い分け

2025年現在、各AIをどのように使い分けているかをメモしておく。

  • ChatGPT(無料版)

    • 適当な雑談
    • 技術のニッチな領域に関する相談
    • 文章添削
    • コーディングなど
  • Gemini(Google AI Pro)

    • ネット検索/Deep Research
    • NotebookLM
    • マルチモーダル(画像/PDFに関する質問など)
    • カスタムGemでさまざまな人格を作って遊ぶ
    • ChatGPTがGPT-5に置き換えられて口調がウザくなってしまったので、今はほぼGeminiメイン
  • Grok(無料版)

    • Xのポストの検索/分析
  • Perplexity(無料版)

    • ネット検索
  • GitHub Copilot Pro

    • プレミアムリクエストを消費してClaude Sonnet 4.5を使う(Agentが強い)
    • プレミアムリクエストを消費しないモデルはどれがいいのか正直よくわからん

2024年を振り返る

毎年恒例、今年を振り返るシリーズ。去年はXの埋め込みを貼りすぎてページが重くなってしまったので、今回はもっとざっくりと書く。

技術関係

自作OS開発は通年営業として、今年はセキュリティにも(少しだけ)力を入れた開発をした。学校の課題も兼ねているなど、割と大がかりなものはブログにまとめていたりするので、ぜひ読んでほしい。

zebian.hatenablog.com zebian.hatenablog.com zebian.hatenablog.com zebian.hatenablog.com

就活の年!

今年を一言で表すならば、「就活の年」だったと思う。6月頃にサマーインターンのエントリーを行い、課題を送ったり面接を受けたり、、、夏休み期間中に3社インターンに参加することができた。

3社ともセキュリティ関係の会社で、内容も盛りだくさんでとても勉強になったし、交通費やホテル代なども全額支給していただき、地方住みにとってはかなりありがたかった。

会社の人もそうだし、他の参加者の方も技術に長けた人間ばかりだったため、技術系の会話が弾んでとても楽しかったし、インターンを通して人脈が広がったりと、とにかくいいことづくめだった。貴重な機会に感謝。

そのうちのある1社がとても気に入り、11~12月頃に早期選考を受け、無事内定をいただくことができた。個人的にとても満足な結果であったため、内定を承諾し、年が明ける前に就活を終わらせることができた。

就活期間が半年足らずというかなりの爆速スケジュールで、同級生の誰よりも早く内定が決まり、想像していたよりもあっけなかった。いろいろな人にアドバイスをいただいてここまでこぎつけることができたので、とにかく感謝しかない。

観光名所巡り

せっかく自分の車を持っているので、今年は地元の観光名所をたくさん回った。インスタの方に写真を上げているのでよかったら見てほしい。

https://www.instagram.com/zakki0925224_/

↑みたいな悩みもありほとんどソロなので、今後はもっと大勢で行きたい。

お絵かき

ChromebookからXiaomi Pad6に移行し、お絵描きアプリが使えるようになったり、マシンスペック的にも快適に描けるようになった。相変わらず下手くそなのでもっと上手くなりたい。

総括

一年が終わるの早すぎる。来年は交通費を貯めてもっと行動の幅を広げたい。