コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2025年12月18日 >>> 2025年12月5日
link もっと後

2025年12月18日

preadとlseek + readは違います

目次: Linux

知っている人には「なんだそんなことか」で終わりな話なんですが、お恥ずかしいことに私は今まで知らなかったのでメモしておきます。

Linuxには指定されたオフセットからデータを読み出すシステムコールpread(pread64)があります。私は今までpread = lseek + readだと思っていましたが、特定の環境だと両者の動作が違います。

まずは確認のためのテストプログラムを用意しました。

lseek + readとpreadを2回ずつ実行するテストプログラム

#include <stdio.h>

#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(int argc, char *argv[])
{
	int fd = -1;
	char buf[256];
	ssize_t nr;
	off_t pos, offset1 = 0x1000, offset2 = 0x2000;

	fd = open("/dev/sample_char/sample_char_dev", O_RDWR);
	if (fd == -1) {
		perror("open");
		goto err_out;
	}

	// lseek + read
	pos = lseek(fd, offset1, SEEK_SET);
	if (pos == (off_t)-1) {
		perror("lseek 1");
	}

	nr = read(fd, buf, sizeof(buf));
	if (nr != (ssize_t)sizeof(buf)) {
		perror("read 1-1");
	}
	nr = read(fd, buf, sizeof(buf));
	if (nr != (ssize_t)sizeof(buf)) {
		perror("read 1-2");
	}

	pos = lseek(fd, offset2, SEEK_SET);
	if (pos == (off_t)-1) {
		perror("lseek 2");
	}

	nr = read(fd, buf, sizeof(buf));
	if (nr != (ssize_t)sizeof(buf)) {
		perror("read 2-1");
	}
	nr = read(fd, buf, sizeof(buf));
	if (nr != (ssize_t)sizeof(buf)) {
		perror("read 2-2");
	}

	// pread
	nr = pread(fd, buf, sizeof(buf), offset1);
	if (nr != (ssize_t)sizeof(buf)) {
		perror("pread 1");
	}

	nr = pread(fd, buf, sizeof(buf), offset2);
	if (nr != (ssize_t)sizeof(buf)) {
		perror("pread 2");
	}

err_out:
	if (fd != -1) {
		close(fd);
		fd = -1;
	}
	return 0;
}

やっていることは、

  • 1000にシーク
  • 256バイトread(位置1000から読み出す)
  • 256バイトread(位置1256から読み出す)
  • 2000にシーク
  • 256バイトread(位置2000から読み出す)
  • 256バイトread(位置2256から読み出す)
  • 1000から256バイトpread(位置1000から読み出す)
  • 2000から256バイトpread(位置2000から読み出す)

こんな操作です。

キャラクタデバイスの用意

ファイルにどんな操作が行われたか知るために、キャラクタデバイスを作成してseekやreadのログを出します。方法は別にキャラクタデバイスでなくても何でも良いんですが、Linuxカーネルモジュールでキャラクタデバイスを実装するのが楽だと思います。ソースコードはGitHubに置きました。

キャラクタデバイスを提供するカーネルモジュールのビルド方法
sudo apt-get install linux-headers-amd64

make
sudo insmod sample-char-dev.ko
gcc test.c -o test
./test

カーネルモジュールのビルド方法とテストプログラムの実行方法は上記のとおりです。

lseekできるファイルの場合

特にカーネルモジュールの実装を変えなければ、キャラクタデバイスのファイル位置はlseekで変更可能かつreadにより進む実装になります。テストプログラムを実行すると下記のようなカーネルログが出るはずです。カーネルログはsudo dmesg -wなどで見てください。

lseekをサポートしている場合のログ
sample_char: open.
sample_char: seek to 1000.
sample_char: read 256 bytes at 1000.    ★lseek + read: 位置1000から読み出せる★
sample_char: read 256 bytes at 1256.
sample_char: seek to 2000.
sample_char: read 256 bytes at 2000.    ★lseek + read: 位置2000から読み出せる★
sample_char: read 256 bytes at 2256.
sample_char: read 256 bytes at 1000.    ★pread: lseek(1000)+readと一緒★
sample_char: read 256 bytes at 2000.    ★pread: lseek(2000)+readと一緒★
sample_char: release.

シングルスレッドかつlseek可能な通常のファイルではlseek + readでも、preadでも結果は同じです。位置1000もしくは位置2000からreadできます。

lseekできないファイルの場合

しかしlseekだけできない特殊なファイルだと結果が異なります。先ほどのキャラクタデバイスのlseekを無効にして試しましょう。

lseekを無効にする

static const struct file_operations sample_char_fops = {
	.owner   = THIS_MODULE,
	.open    = sample_char_fops_open,
	.release = sample_char_fops_release,
	.read    = sample_char_fops_read,
	.write   = sample_char_fops_write,
//	.llseek  = sample_char_fops_llseek,    ★この行をコメントアウトする★
};

変更によりlseekだけ失敗し、readは成功するファイル(テストプログラムがIllegal seekのエラー表示を出す)になりました。この状態でテストプログラムを実行すると下記のようなカーネルログが出るはずです。カーネルログはsudo dmesg -wなどで見てください。

lseekをサポートしていない場合のログ
sample_char: open.
sample_char: read 256 bytes at 0.       ★lseek + read: 位置1000から読み出しているつもりが0からになる★
sample_char: read 256 bytes at 256.
sample_char: read 256 bytes at 512.     ★lseek + read: 位置2000から読み出しているつもりが512からになる★
sample_char: read 256 bytes at 768.
sample_char: read 256 bytes at 1000.    ★pread: 変わらず位置1000から読み出せる★
sample_char: read 256 bytes at 2000.    ★pread: 変わらず位置2000から読み出せる★
sample_char: release.

最初はlseek + readのログですが、位置1000もしくは2000からreadしたいのに、lseekが失敗するため意図しない位置を読み出してしまう一方、preadはlseekと無関係に指定された位置1000もしくは2000から読み出せます。

実例

個人的にはファイル位置に意味があるのにlseekできないファイルを作るのは良くない実装だと思いますが、Linuxカーネルにもこのような実装が存在しています。ちょうど最近出会ったvfioがlseek無効の実装になっていました。vfioはファイル位置によってデバイスのどのメモリ領域を読み出せるか変わります。例えばvfio-pciであればBAR 0はこの位置、BAR 1はこの位置、みたいに決まっています。

vfioのデバイスファイルのファイル操作

// linux/drivers/vfio/vfio_main.c

const struct file_operations vfio_device_fops = {
	.owner		= THIS_MODULE,
	.open		= vfio_device_fops_cdev_open,
	.release	= vfio_device_fops_release,
	.read		= vfio_device_fops_read,
	.write		= vfio_device_fops_write,
	.unlocked_ioctl	= vfio_device_fops_unl_ioctl,
	.compat_ioctl	= compat_ptr_ioctl,
	.mmap		= vfio_device_fops_mmap,
};

しかし上記のようにvfioにはllseekの定義がありません。lseek+readではなくpreadで読み出さないと正常に動作しないことに気づくまでかなり困惑しました。こういう実装は良くないと思うんだ……。

編集者:すずき(2025/12/20 19:11)

コメント一覧

  • hdkさん(2025/12/21 08:34)
    昔試しにデバイスドライバーを作ったことがあった気がするのによく覚えていませんでしたが、seekやreadでf_posを更新するのはデバイスドライバー側の仕事なんですね。SEEK_SETなんかの解釈はどのデバイスドライバーでも共通、ってわけでもないのかぁ。pread/pwriteはオフセットを変更しないでアクセスできるので、マルチスレッドで使う時に便利、くらいの認識でした。つまりlseek+readとの違いはオフセットが変わらないことで、シングルスレッドであっても、preadの後にlseekせずにreadを使えばpreadでオフセットが変化しないことが観察できたかと思います。
  • すずきさん(2025/12/23 23:15)
    ですね、まあpread+readだと話が混ざるかなーと思って分けてみました。

    私もオフセットの管理を勝手にやってくれないんだな〜って思いましたけど、fifoみたいなオフセットの概念がないやつもいるし、ファイルに必ずファイルオフセットがあるわけじゃないので、こういう実装なんでしょうねえ。
  • すずきさん(2025/12/23 23:51)
    良く見たらksys_read()でfile->f_posを更新してくれているので、ドライバでfile->f_pos弄るの良くないのかも……??
open/close この記事にコメントする



2025年12月16日

initramfsの更新方法

目次: Linux

いつも忘れてググっている気がするのでメモしておきます。Linuxカーネルを変更したときにinitramfsを更新する方法です。更新にはupdate-initramfsコマンドを使用します。例えばカーネルバージョン6.18.2だとしたら、

update-initramfsの実行例
#### 1つのinitramfsを狙い撃ちで更新

# update-initramfs -k 6.18.2 -u


#### 全てのinitramfsを更新

# update-initramfs -k all -u

ツールが/boot/以下にあるconfig-(kernel version)を見ていい感じにinitramfsの圧縮形式などを調整してくれます。もしカーネルバージョンの指定を間違ったり、configファイルのコピーを忘れたりすると下記のような警告が出ます。

configが見つからないときのupdate-initramfsの警告
# update-initramfs -k aaaa -u

update-initramfs: Generating /boot/initrd.img-aaaa
W: Kernel configuration /boot/config-aaaa is missing, zstd compression support (CONFIG_RD_ZSTD) cannot be checked and is assumed to be available

カーネルとinitramfsを更新したらupdate-grub2も実行しておきましょう。

編集者:すずき(2025/12/24 00:47)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年12月14日

エアガンの外部ソース化キットを一部購入

目次: 射的

アキバのターゲット1にて減圧レギュレータ(0.4MPa)とホース、コネクタを買いました。いわゆるエアガンを外部から供給するガスで動作させるための部品「外部ソースキット」と呼ばれるものの一部です。

ガスガンを作動させるには何らかの圧縮ガスが必要で、ざっくりいってこの3パターンがあります。

動作方式動作ガス外部ソースキット空き缶デメリット
フロン 液化フロンガス不要発生する 冷えに弱い、速く動かすと液化ガスが噴き出る
外部ソース CO2ボンベ 必要発生する 必要な道具が多くやや高い
エアーコンプレッサー圧縮空気 必要発生しないホースが必要、コンプレッサーの近くでしか使えない

私はずっとフロンガスだったので、エアガンの動作用フロンガスの空き缶が家にどんどん溜まります。空き缶は500ccのPETボトルくらいあって割と邪魔で、なんとかならないかなーと思っていました。

CO2外部ソースは知っていたものの空き缶が発生することに変わりはなくて、フロンガスの空き缶がCO2ガスの空き缶に代わっても嬉しくないので購入を見送っていました。

最近になって、アキバのターゲット1にエアーコンプレッサーが設置されたため(しかも今のところは無料で使わせていただける)、これが決め手になって外部ソースキットの一部購入に踏み切りました。家にフロンガスの空き缶が増えるのが防げるのはかなりありがたいです。

編集者:すずき(2025/12/17 23:58)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年12月11日

Ubuntuのカーネルパニック画面

目次: Linux

Ubuntu 24.04 LTSで起動中にカーネルパニックを起こすとこんな感じの画面になります。画面にはエラーメッセージの最後と思われる1行のみ表示され、他のカーネル起動ログは隠されます。


Ubuntuのカーネルパニック画面

エラーの原因がわかりにくくて開発の邪魔なので無効にするべく調べました。どうやらカーネルの設定らしくてCONFIG_DRM_PANICを有効/無効にするとこの画面が出る/出ないが切り替わります。

Ubuntu風の配色にする設定
Device Drivers  --->
  Graphics support  --->
    <M> Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)  --->
      [*]   Display a user-friendly message when a kernel panic occurs
        (0xffffff) Drm panic screen foreground color, in RGB
        (0x5e2750) Drm panic screen background color, in RGB

Ubuntu 24.04 LTSと同じ配色にしたい場合は、上記のような設定にすると良いです。何がユーザーフレンドリーかわかんないし、私はこの機能は要らんです……。

編集者:すずき(2025/12/19 23:59)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年12月10日

LinuxからBIOS/UEFIの設定を取得する

目次: Linux

設定によって何か動作を変えたい、PC再起動するのが嫌など、BIOS/UEFIの設定をLinuxから見たいときがあります。お使いのPCがThinkPadであればfwupdmgrが使えると思います。

fwupdmgrでBIOS/UEFIの設定を取得
$ sudo apt-get install fwupd

$ sudo fwupdmgr get-bios-settings

SecureBoot:
  Setting type:         Enumeration
  Current Value:        Disable
  Description:          SecureBoot
  Read Only:            True
  Possible Values:
    0:                  Disable
    1:                  Enable

LockBIOSSetting:
  Setting type:         Enumeration
  Current Value:        Disable
  Description:          LockBIOSSetting
  Read Only:            False
  Possible Values:
    0:                  Disable
    1:                  Enable

...

HWによって対応している場合と対応していない場合があるようで、家の自作デスクトップPCだと下記のように怒られてしまって設定取得できませんでした。

fwupdmgrを自作PCで動かしたときのエラー
$ sudo fwupdmgr get-bios-settings

WARNING: UEFI ESP partition not detected or configured
See https://github.com/fwupd/fwupd/wiki/PluginFlag:esp-not-found for more information.
WARNING: UEFI firmware can not be updated in legacy BIOS mode
See https://github.com/fwupd/fwupd/wiki/PluginFlag:legacy-bios for more information.
Authenticating?          [ -                                     ]
This system doesn't support firmware settings

メッセージ曰く「UEFI ESPパーティションがない」「legacy BIOSモードだとダメ」と言っています。これ以上は深追いしていないため解決方法がわからんのですが、何か追加で設定が要るんですかねえ?

編集者:すずき(2025/12/24 01:02)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2025年12月8日

LXPanelのボタン入れ替えが使えないときの直し方

目次: Linux

LXDEにはLXPanelといってタスクバーやスタートメニューなどを表示しているアプリがいます。LXPanelのタスクバーにはドラッグするとボタンを入れ替えられる機能がありますが、LXPanelのボタン入れ替え機能はバグっているようで、たびたび動かなくなります。起動直後に発生率高い気がします。

ボタン入れ替え機能が死んでしまったときは、LXPanelの何もないところを右クリックして[パネルの設定]を選び、設定ウインドウを表示させましょう。


下記のように操作すると直ることが多いです。

  • [高度な設定] - [使わないときはパネルを最小化する]をON
  • タスクバーが消えるまで待つ
  • カーソルをタスクバーに持って行く
  • タスクバーが表示される
  • この時点でタスクバーのボタンが入れ替えられる(なってなかったら諦める)
  • タスクバーが消えないうちに[使わないときはパネルを最小化する]をOFF

私の環境(Debian Testing, forky/sid)だと、1回でダメでも何回かやったら直ることもあります。どうやっても直らないときもありますから、その時は諦めて再起動ですね……。

編集者:すずき(2025/12/20 21:48)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2025年12月18日 >>> 2025年12月5日
link もっと後

管理用メニュー

link 記事を新規作成

<2025>
<<<12>>>
-123456
78910111213
14151617181920
21222324252627
28293031---

最近のコメント5件

  • link 25年12月18日
    すずきさん (12/23 23:51)
    「良く見たらksys_read()でfil...」
  • link 25年12月18日
    すずきさん (12/23 23:15)
    「ですね、まあpread+readだと話が...」
  • link 25年12月18日
    hdkさん (12/21 08:34)
    「昔試しにデバイスドライバーを作ったことが...」
  • link 25年11月28日
    hdkさん (12/04 08:10)
    「あれ、停止直前くらいの時のトルクコンバー...」
  • link 25年11月28日
    すずきさん (12/03 11:24)
    「トルクコンバーターがいてエンブレは掛かり...」

最近の記事3件

  • link 23年4月10日
    すずき (12/24 01:05)
    「[Linux - まとめリンク] 目次: Linuxカーネル、ドライバ関連。Linux kernel 2.4 for ARMが...」
  • link 25年12月10日
    すずき (12/24 01:02)
    「[LinuxからBIOS/UEFIの設定を取得する] 目次: Linux設定によって何か動作を変えたい、PC再起動するのが嫌な...」
  • link 25年12月16日
    すずき (12/24 00:47)
    「[initramfsの更新方法] 目次: Linuxいつも忘れてググっている気がするのでメモしておきます。Linuxカーネルを...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 2025年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 12/24 01:05