Linuxカーネルのドキュメント

Linuxカーネルドキュメント (公式)

Linuxカーネルの公式のドキュメントである。
Quick searchで検索するか、Contentsから項目を探す。

例えば、CANインターフェースについては、
Subsystems → Network Interfaces → Networking → SocketCAN - Controller Area Network
に書かれている。

過去のLinuxカーネルのバージョンについては下記から。
https://www.kernel.org/doc/html/

その他のドキュメントについては下記から。
https://www.kernel.org/doc/

Doxygenドキュメント (野良)

かなりバージョン(3.7)のまま更新されていないが、Doxygenで生成したLinuxカーネルソースのドキュメントが下記で閲覧できる。
https://docs.huihoo.com/doxygen/linux/kernel/3.7/

ヘッダファイルを読む

C言語のヘッダファイルは /usr/include/ にある。
stdio.h は /usr/include/stdio.h にある。
net/if.h は /usr/include/net/if.h にある。

しかし、別の場所にある場合もある。
例えば、Ubuntu20.04 (x64版) では、
sys/types.h は /usr/include/x86_64-linux-gnu/sys/types.h にある。

ヘッダファイルがどこにあるかは locate コマンドで確認できる。
(Ubuntu等であれば、locate は apt でインストールできる。)

$ locate sys/types.h
/usr/include/x86_64-linux-gnu/sys/types.h

または、findコマンドで探す。

$ find /usr/include -name types.h

ググるなカス

Google検索が劣化した」とか「ChatGPT使え」とかいう話ではなく。

manコマンド

UNIX系OSのコマンドやシステムコールC言語の標準関数などについては、man コマンドでマニュアルを読むことができる。

(例) atan2関数のマニュアル

$ man atan2

ただし、printf のように、UNIXのコマンドとC言語の標準関数で同じ名前のものがあるような場合には、セクションを指定する。

セクション 分類
1 コマンド
2 システムコール (UNIX系OSが提供する関数)
3 ライブラリ関数
4 バイス(/dev/の特殊ファイル)やドライバ
5 構成ファイルの書式
6 ゲーム
7 その他
8 システム管理コマンド

(例) printf関数のマニュアル

$ man 3 printf

どのセクションにあるか調べるには -f オプションを付ける。

$ man -f printf
printf (1)           - format and print data
printf (3)           - formatted output conversion

JM Project

しかしまあ、日本語のほうが読みやすいし、Webページ版のほうが便利ではある。日本語訳の man page のWebサイトとして JM Project がある。ところが、2024年5月現在、本家(OSDN)は接続できなくなっている。(経緯についてはこちらを参照)

英文のオンラインman page

英文のLinuxのオンラインman page としては、下記のものなどがある。

C言語:標準入力と矢印キー

C言語のプログラムで矢印キーの入力を受けるにはどうすればよいか?

Windowsの場合

_getch() でキー入力を受け取ると、矢印キーなどは以下のような値になる。

キー キー
0xE0, 0x48(H) Home 0xE0, 0x47(G)
0xE0, 0x50(P) End 0xE0, 0x4F(O)
0xE0, 0x4D(M) PgUp 0xE0, 0x49(I)
0xE0, 0x4B(K) PgDn 0xE0, 0x51(Q)
Enter 0x0D(CR) Delete 0xE0, 0x53(S)
Esc 0x1B(ESC) BackSpase 0x08(BS)
#include <stdio.h>
#include <conio.h>

int main()
{
    while (1) {
        while (!_kbhit()) {;}
        printf("kbhit: ");
        int ch = _getch();
        if (ch == 0xE0) {
            ch = _getch();
            switch (ch) {
            case 0x48: printf("Up\n");    break;
            case 0x50: printf("Down\n");  break;
            case 0x4D: printf("Right\n"); break;
            case 0x4B: printf("Left\n");  break;
            default: printf("Special Key %02X\n", ch); break;
            }
            continue;
        }
        printf("'%c' (%02X)\n", ch, ch);
        if (ch == 'q') break;
    }
    printf("\nQuit\n");
    return 0;
}

Linuxの場合 (1)

以前の記事で、Linuxで kbhit() / getch() 相当の機能を実現する方法を述べた。
関連:Linuxで kbhit() と getch() - 滴了庵日録

この場合、矢印キーなどは以下のような値になる。

キー キー
0x1B(ESC), 0x5B([), 0x41(A) Home 0x1B(ESC), 0x5B([), 0x48(H)
0x1B(ESC), 0x5B([), 0x42(B) End 0x1B(ESC), 0x5B([), 0x46(F)
0x1B(ESC), 0x5B([), 0x43(C) PgUp 0x1B(ESC), 0x5B([), 0x35(5), 0x7E(~)
0x1B(ESC), 0x5B([), 0x44(D) PgDn 0x1B(ESC), 0x5B([), 0x36(6), 0x7E(~)
Enter 0x0A(LF) Delete 0x1B(ESC), 0x5B([), 0x33(3), 0x7E(~)
Esc 0x1B(ESC) BackSpase 0x7F(DEL)
#include <stdio.h>
#include "kbhit.h"

int main()
{
    kb_begin();

    while (1) {
        while (!kbhit()) { ; }
        printf("kbhit: ");
        int ch = getch();
        if (ch == 0x1B) {
            ch = getch();
            if(ch == 0){
                printf("ESC\n");
            }
            else if(ch == '['){
                ch = getch();
                switch (ch) {
                case 'A': printf("Up\n");    break;
                case 'B': printf("Down\n");  break;
                case 'C': printf("Right\n"); break;
                case 'D': printf("Left\n");  break;
                default: printf("Special Key %02X\n", ch); break;
                }
            }
            continue;
        }
        printf("'%c' (%02X)\n", ch, ch);
        if (ch == 'q') break;
    }
    printf("\nQuit\n");
    kb_end();

    printf("\nQuit\n");
    return 0;
}

Linuxの場合 (2)

もう一つの方法として、ncursesライブラリを使うやりかたもある。

まず、Ubuntu等では下記のコマンドでライブラリをインストールする。

sudo apt install libncurses5-dev

ncursesライブラリでは、矢印キーなどは以下のような値になる。

キー 定数名 / 値 キー 定数名
KEY_UP Home KEY_HOME
KEY_DOWN End KEY_END
KEY_RIGHT PgUp KEY_PPAGE
KEY_LEFT PgDn KEY_NPAGE
Enter 0x0A(LF) Delete KEY_DC
Esc 0x1B(ESC) BackSpase KEY_BACKSPACE
#include <ncurses.h>

int main()
{
    initscr(); // ncursesモードの初期化
    raw();     // 入力をバッファリングせずに受け取る      
    noecho();  // 入力文字を画面に表示しない
    keypad(stdscr, TRUE); // 特殊キー入力を有効にする      

    while (1) {
        int ch = getch();
        printw("getch: ");
        switch(ch) {
            case KEY_UP:    printw("Up\n");    break;
            case KEY_DOWN:  printw("Down\n");  break;
            case KEY_RIGHT: printw("Right\n"); break;
            case KEY_LEFT:  printw("Left\n");  break;
            default: 
                printw("'%c' (%02X)\n", ch, ch);
        }
        if (ch == 'q') break;
        refresh();  // 画面を更新
    }
    printf("\nQuit\n");
    endwin();  // ncursesモードの終了
    return 0;
}

printf()のかわりにprintw()を使うことに注意。
ビルドは以下のようにライブラリを指定する。

gcc main.c -lncurses

C言語でログ出力のレベル分け

  • 下記のようなマクロを使用してログ出力のレベルを制御する。
  • さらに細かく、FATAL(致命的なエラー)、ERROR(エラー)、WARN(警告)、INFO(情報)、DEBUG(デバッグ情報)、TRACE(トレース情報)のように分けてもよい。
// デバッグ出力
#define DEBUG_LOG_LEVEL  2

#if DEBUG_LOG_LEVEL >= 4
#define DEBUG_LOG_TRACE(fmt, ...) printf("[TRACE]" fmt, ##__VA_ARGS__)
#else
#define DEBUG_LOG_TRACE(fmt, ...) /* 何もしない */
#endif
#if DEBUG_LOG_LEVEL >= 3
#define DEBUG_LOG_DEBUG(fmt, ...) printf("[DEBUG]" fmt, ##__VA_ARGS__)
#else
#define DEBUG_LOG_DEBUG(fmt, ...) /* 何もしない */
#endif
#if DEBUG_LOG_LEVEL >= 2
#define DEBUG_LOG_INFO(fmt, ...) printf("[INFO]" fmt, ##__VA_ARGS__)
#else
#define DEBUG_LOG_INFO(fmt, ...) /* 何もしない */
#endif
#if DEBUG_LOG_LEVEL >= 1
#define DEBUG_LOG_ERROR(fmt, ...) printf("[ERROR]" fmt, ##__VA_ARGS__)
#else
#define DEBUG_LOG_ERROR(fmt, ...) /* 何もしない */
#endif

Linuxで kbhit() と getch()

やりたいこと

  • WindowsC言語では kbhit() と getch() を使ってキーボード入力を即時に取得できる。
  • LinuxC言語ではこれらの関数は用意されておらず、キーボード入力は1行ごとにバッファされ、Enterキーが入力されるまで取得できない。
  • Linuxでも kbhit() と getch() 相当の機能を実現したい。

ちなみに、Windowsでも現在では kbhit() と getch() は非推奨の関数名で、代わって _kbhit() と _getch() を使う。

#include <stdio.h>
#include <conio.h>

int main()
{
    while (1) {
        while (!_kbhit()) {;}
        int ch = _getch();
        printf("kbhit: %c\n", ch);
        if (ch == 'q') break;
    }
    printf("\nQuit\n");
    return 0;
}

方法

  • 方法はいろいろあるが、基本的には tcgetattr() / tcsetattr() でターミナルの設定を変更する。
  • 後述の参考サイトのソースを参考に実装した。
  • 処理の無駄を省いたかわり、最初に kb_begin()、最後に kb_end() を実行する。
  • 無駄を省いた作りにしたので使い方を間違えないように注意。
  • スレッドセーフでない作りだが、そもそもキーボード入力を複数のスレッドから受けることはないはず。

ソース

kbhit.h

#pragma once

void kb_begin(void);
void kb_end(void);
int  kbhit(void);
char getch(void);

kbhit.c

#include <stdio.h>
#include <termios.h>
#include <unistd.h>

#include "kbhit.h"

static struct termios g_old_set; // ターミナルの元の設定
static int g_read_char = -1;     // 読みだした文字 (-1のとき無効)

// 開始する
// (標準入力を、即時、エコーバックなし、ノンブロッキングに設定)
void kb_begin(void)
{
    // 現在の標準入力の設定を取得
    tcgetattr(STDIN_FILENO, &g_old_set);
    // 標準入力の設定を変更
    struct termios new_set = g_old_set;
    new_set.c_lflag &= ~ICANON; // 非カノニカル(改行を待たず即時入力)
    new_set.c_lflag &= ~ECHO;   // エコーバックなし
//  new_set.c_lflag &= ~ISIG;   // シグナル発生なし (Ctrl-Cなどの)
    new_set.c_cc[VMIN] = 0;     // 待ち受けしない (待ち受け文字数ゼロ)
    new_set.c_cc[VTIME] = 0;    // 待ち受けしない (待ち受け時間ゼロ)
    tcsetattr(STDIN_FILENO, TCSANOW, &new_set);
}

// 終了する
// (キーボード入力の設定を元に戻す)
void kb_end(void)
{
    // ターミナルの設定を戻す
    tcsetattr(STDIN_FILENO, TCSANOW, &g_old_set);
}

// キーボード入力があったかチェック
// return: 0:なし / 1:あり
int  kbhit(void)
{
    if(g_read_char != -1) return 1; // すでに入力があった
    
    // 標準入力から1文字読み出し
    char ch;
    int num = read(STDIN_FILENO, &ch, 1);
    // 読み出せたか?
    if(num == 1) {
        g_read_char = ch;
        return 1;
    }else{
        return 0;
    }
}

// キーボード入力を1文字取得
char getch(void)
{
    // すでに読みだした文字があればそれを返す
    if(g_read_char != -1) {
       char ch = g_read_char;
       g_read_char = -1;
       return (ch);
    }
    // なければ1文字読み出す (読み出せなければゼロを返す)
    else{
        char ch = 0;
        read(STDIN_FILENO, &ch, 1);
        return(ch);
    }
}

使い方

main.c

#include <stdio.h>
#include "kbhit.h"

int main()
{
    kb_begin();

    while (1) {
        while (!kbhit()) {;}
        char ch = getch();
        printf("kbhit: %c\n", ch);
        if (ch == 'q') break;
    }
    kb_end();

    printf("\nQuit\n");
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)
project(kbhit_test C)
add_executable(kbhit_test main.c kbhit.c)

参考


VirtualBoxのUbuntuのC言語でゲームパッド

VirtualBoxUbuntuC/C++で、USB接続のゲームパッド/ジョイスティックを扱いたい。

まずWindowsで動作確認

「コントロールパネル」→「デバイスとプリンター」でゲームパッドを選択し、右クリックで「ゲームコントローラの設定」→「プロパティ」の「テスト」タブで動作を確認する。


VirtualBoxゲームパッドをゲストOSに接続

仮想マシンの「デバイス」→「USB」から、ゲームパッドをゲストOSに接続する。

Ubuntuで動作確認

USBデバイスとして認識されていることを確認する。
以下の例では、ゲームパッド「JC-U3912T」が認識されていることが分かる。

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 007: ID 056e:200e Elecom Co., Ltd JC-U3912T
Bus 002 Device 002: ID 80ee:0021 VirtualBox USB Tablet
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

jstest-gtkをインストール。

$ sudo apt-get install jstest-gtk

jstest-gtkを実行する。

$ jstest-gtk

この例では、/dev/input/js2 が所望のゲームパッド「JC-U3912T」であることが分かる。
ゲームパッドを選択して「Properties」を開くと、ゲームパッドの動作確認ができる。


C/C++ゲームパッドを扱う

#include <vector>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/joystick.h>

using namespace std;

// ゲームパッドのデバイスを指定
#define JOY_DEV "/dev/input/js2"

int main()
{
  int joy_fd = -1;          // ゲームパッドのファイル指定子
  int num_of_axis    = 0;   // 軸の数
  int num_of_buttons = 0;   // ボタンの数
  char name_of_joystick[80];// ゲームパッドの名前
  vector<int>  joy_axis;    // 各軸の値
  vector<char> joy_button;  // 各ボタンの値

  // ゲームパッドを開く
  if((joy_fd=open(JOY_DEV, O_RDONLY)) < 0) {
    printf("Failed to open %s\n", JOY_DEV);
    return -1;
  }
  // ゲームパッドの軸の数、ボタンの数、名前を取得
  ioctl(joy_fd, JSIOCGAXES, &num_of_axis);
  ioctl(joy_fd, JSIOCGBUTTONS, &num_of_buttons);
  ioctl(joy_fd, JSIOCGNAME(80), &name_of_joystick);

  printf("Joystick: %s\n", name_of_joystick);
  printf("Axis:     %d\n", num_of_axis);
  printf("Buttons:  %d\n", num_of_buttons);

  // Enter入力待ち
  printf("\nHit Enter key\n");
  while ( getchar() != '\n') { ; }

  // 軸の値、ボタンの値を取得するためのメモリ確保
  joy_button.resize(num_of_buttons,0);
  joy_axis.resize(num_of_axis,0);

  // ファイル制御:ノンブロッキングモード
  fcntl(joy_fd, F_SETFL, O_NONBLOCK);

  while(true)
  {
    // イベント取得
    js_event js;
    read(joy_fd, &js, sizeof(js_event));

    // イベント種別ごとの処理
    switch (js.type & ~JS_EVENT_INIT)
    {
    // 軸イベント
    case JS_EVENT_AXIS:
      // 軸番号のチェック
      if((int)js.number >= num_of_axis){
        printf("Axis number error: %d\n", (int)js.number);
        continue;
      }
      // 軸の値の取得
      joy_axis[(int)js.number] = js.value;
      break;
    // ボタンイベント
    case JS_EVENT_BUTTON:
      // ボタン番号のチェック
      if((int)js.number >= num_of_buttons){
        printf("Button number error: %d\n", (int)js.number);
        continue;
      }
      // ボタンの値の取得
      joy_button[(int)js.number] = js.value;
      break;
    }

    // 表示
    printf("Axis:");
    for(int i = 0; i < num_of_axis; i++){ printf(" %6d", joy_axis[i]); }
    printf("\n");
    printf("Button:");
    for(int i = 0; i < num_of_buttons; i++){ printf(" %d", joy_button[i]); }
    printf("\n");

    usleep(1000);
  }
  // ゲームパッドを閉じる
  close(joy_fd);
  return 0;
}

参考


エスケープシーケンスまとめ

エスケープシーケンスとは?

コンソールに、\033 または \x1b から始まる文字を出力することでコンソールの表示を制御する。
8進数 033 は、16進数で 0x1b、10進数で27であり、ESCを表す。

画面消去

文字列 説明
\033[0J カーソル位置から画面右下まで消去
\033[1J カーソル位置から画面左上まで消去
\033[2J 全画面消去
\033[0K カーソル位置から行末まで消去
\033[1K カーソル位置から行頭まで消去
\033[2K カーソル位置の行を消去

カーソル移動

文字列 説明
\033[nA カーソルをn行上へ移動
\033[nB カーソルをn行下へ移動
\033[nC カーソルをn桁右へ移動
\033[nD カーソルをn桁左へ移動
\033[r;cH カーソルをr行、c列目へ移動

文字修飾

文字列 説明
\033[1m 強調(太字)
\033[4m 下線
\033[7m 文字色と背景色の反転
\033[0m 標準に戻す

文字色

文字列 説明
\033[30m
\033[31m
\033[32m
\033[33m 黄色
\033[34m
\033[35m マゼンタ
\033[36m シアン
\033[37m
\033[39m 標準色に戻す

背景色

文字列 説明
\033[40m
\033[41m
\033[42m
\033[43m 黄色
\033[44m
\033[45m マゼンタ
\033[46m シアン
\033[47m
\033[49m 標準色に戻す

マクロ

以下のようなマクロを定義しておくと便利かもしれない。

// 消去
#define CLR_SCR()       printf("\033[2J") // 全画面消去
#define CLR_RIGHT()     printf("\033[0K") // カーソル位置から行末まで消去
#define CLR_LEFT()      printf("\033[1K") // カーソル位置から行頭まで消去
#define CLR_LINE()      printf("\033[2K") // カーソル位置の行を消去

// カーソル移動
#define LOCATE(r,c)     printf("\033[%d;%dH", r, c)  //カーソル位置を移動

// 文字修飾
#define CHR_BOLD        "\033[1m"   // 太字
#define CHR_REVERT      "\033[7m"   // 文字色反転
#define CHR_RESET       "\033[0m"   // 通常に戻す

// 文字色
#define CHR_BLACK       "\033[30m"  //黒
#define CHR_RED         "\033[31m"  //赤
#define CHR_GREEN       "\033[32m"  //緑
#define CHR_YELLOW      "\033[33m"  //黄色
#define CHR_BLUE        "\033[34m"  //青
#define CHR_MAGENTA     "\033[35m"  //マゼンタ
#define CHR_CYAN        "\033[36m"  //シアン
#define CHR_WHITE       "\033[37m"  //白
#define CHR_NORMAL      "\033[39m"  //標準色に戻す

// 背景色
#define BG_BLACK        "\033[40m"  //黒
#define BG_RED          "\033[41m"  //赤
#define BG_GREEN        "\033[42m"  //緑
#define BG_YELLOW       "\033[43m"  //黄色
#define BG_BLUE         "\033[44m"  //青
#define BG_MAGENTA      "\033[45m"  //マゼンタ
#define BG_CYAN         "\033[46m"  //シアン
#define BG_WHITE        "\033[47m"  //白
#define BG_NORMAL       "\033[49m"  //標準色に戻す

使い方

上記のマクロ定義を esc.h としてインクルード。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "esc.h"

int main(void)
{
    int count = 0;

    CLR_SCR();
    LOCATE(1, 1);
    printf(CHR_BOLD CHR_GREEN "Escape Sequence Test");

    LOCATE(3, 1);
    printf(CHR_BLUE "count:  \n");
    printf(CHR_BLUE "random: \n");
    printf(CHR_RESET);
    fflush(stdout);

    while(1){
        LOCATE(3, 9); CLR_RIGHT();
        printf("%4d", count);
        LOCATE(4, 9); CLR_RIGHT();
        printf("%4d", rand() % 10000);
        printf("\n");
        count++;
        usleep(500 * 1000);
    }
    return 0;
}