コグノスケ


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)

コメント一覧

  • コメントはありません。
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 この記事にコメントする



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

管理用メニュー

link 記事を新規作成

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

最近のコメント5件

  • link 25年11月28日
    hdkさん (12/04 08:10)
    「あれ、停止直前くらいの時のトルクコンバー...」
  • link 25年11月28日
    すずきさん (12/03 11:24)
    「トルクコンバーターがいてエンブレは掛かり...」
  • link 25年11月28日
    hdkさん (12/02 08:02)
    「"停止直前に急にエンブレがほぼゼロになる...」
  • link 25年10月6日
    すずきさん (10/10 13:14)
    「ですね。ccはもはやコンパイラというより...」
  • link 25年10月6日
    hdkさん (10/10 08:27)
    「ただのHello, worldでも試して...」

最近の記事3件

  • link 23年4月10日
    すずき (12/20 19:11)
    「[Linux - まとめリンク] 目次: Linuxカーネル、ドライバ関連。Linux kernel 2.4 for ARMが...」
  • link 25年12月18日
    すずき (12/20 19:11)
    「[preadとlseek + readは違います] 目次: Linux知っている人には「なんだそんなことか」で終わりな話なんで...」
  • link 25年12月11日
    すずき (12/19 23:59)
    「[Ubuntuのカーネルパニック画面] 目次: LinuxUbuntu 24.04 LTSで起動中にカーネルパニックを起こすと...」
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/20 19:11