コグノスケ


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

link もっと前
2025年12月19日 >>> 2025年12月6日
link もっと後

2025年12月19日

preadとlseek + readは何が違う?

目次: Linux

前回(2025年12月18日の日記参照)はpreadとlseek + readの違いを見るため、テストプログラムを作って動作確認しました。今回はLinuxカーネルの実装の違いを見ます。

preadの実装

カーネルバージョンは6.9です。最近のバージョンであればさほど実装は変わらないと思います。

Linuxのpread実装

// linux/fs/read_write.c

SYSCALL_DEFINE4(pread64, unsigned int, fd, char __user *, buf,
			size_t, count, loff_t, pos)
{
	return ksys_pread64(fd, buf, count, pos);
}

ssize_t ksys_pread64(unsigned int fd, char __user *buf, size_t count,
		     loff_t pos)
{
	if (pos < 0)
		return -EINVAL;

	CLASS(fd, f)(fd);
	if (fd_empty(f))
		return -EBADF;

	if (fd_file(f)->f_mode & FMODE_PREAD)
		return vfs_read(fd_file(f), buf, count, &pos);

	return -ESPIPE;
}

まずはpreadの実装です。pread64システムコールはksys_pread64()を呼び出します。ksys_pread64()はvfs_read()にバッファ、サイズ、オフセット(= ファイル位置)のパラメータをそのまま渡します。ファイルディスクリプタが一切登場しません。シンプルですね。vfs_read()の先は基本的に各ドライバのread実装になります。

lseekとreadの実装

次はlseek+readの実装です。lseekから見ましょう。

lseekの実装

// linux/fs/read_write.c

SYSCALL_DEFINE3(lseek, unsigned int, fd, off_t, offset, unsigned int, whence)
{
	return ksys_lseek(fd, offset, whence);
}

static off_t ksys_lseek(unsigned int fd, off_t offset, unsigned int whence)
{
	off_t retval;
	CLASS(fd_pos, f)(fd);
	if (fd_empty(f))
		return -EBADF;

	retval = -EINVAL;
	if (whence <= SEEK_MAX) {
		loff_t res = vfs_llseek(fd_file(f), offset, whence);
		retval = res;
		if (res != (loff_t)retval)
			retval = -EOVERFLOW;	/* LFS: should only happen on 32 bit platforms */
	}
	return retval;
}

loff_t default_llseek(struct file *file, loff_t offset, int whence)
{
	struct inode *inode = file_inode(file);
	loff_t retval;

	retval = inode_lock_killable(inode);
	if (retval)
		return retval;
	switch (whence) {
		case SEEK_END:
			offset += i_size_read(inode);
			break;
		case SEEK_CUR:
			if (offset == 0) {
				retval = file->f_pos;
				goto out;
			}
			offset += file->f_pos;
			break;
		case SEEK_DATA:
			/*
			 * In the generic case the entire file is data, so as
			 * long as offset isn't at the end of the file then the
			 * offset is data.
			 */
			if (offset >= inode->i_size) {
				retval = -ENXIO;
				goto out;
			}
			break;
		case SEEK_HOLE:
			/*
			 * There is a virtual hole at the end of the file, so
			 * as long as offset isn't i_size or larger, return
			 * i_size.
			 */
			if (offset >= inode->i_size) {
				retval = -ENXIO;
				goto out;
			}
			offset = inode->i_size;
			break;
	}
	retval = -EINVAL;
	if (offset >= 0 || unsigned_offsets(file)) {
		if (offset != file->f_pos)
			file->f_pos = offset;
		retval = offset;
	}
out:
	inode_unlock(inode);
	return retval;
}
EXPORT_SYMBOL(default_llseek);

先ほど同様にlseekシステムコールはksys_lseek()を呼び、ksys_lseek()はvfs_llseek()を呼ぶだけです。vfs_llseek()の先は各ドライバの実装になります。特にこだわりがない場合は通常のlseekの振る舞いをするdefault_llseek()を使うことが多いので、default_llseek()の実装を載せておきました。

素直にwhenceの値に従ってoffsetを計算(例: SEEK_SETの場合はoffsetの値をそのまま使う、SEEK_CURの場合は現在のfile->f_posを足す)し、ファイルディスクリプタのメンバ(file->f_pos)に設定しています。

readの実装

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
	return ksys_read(fd, buf, count);
}

ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
	CLASS(fd_pos, f)(fd);
	ssize_t ret = -EBADF;

	if (!fd_empty(f)) {
		loff_t pos, *ppos = file_ppos(fd_file(f));
		if (ppos) {
			pos = *ppos;
			ppos = &pos;
		}
		ret = vfs_read(fd_file(f), buf, count, ppos);
		if (ret >= 0 && ppos)
			fd_file(f)->f_pos = pos;
	}
	return ret;
}

最後にreadの実装です。readシステムコールはksys_read()を呼び出しており、ksys_read()はファイルディスクリプタからオフセットを取得(fd_file(f)->f_pos)して、vfs_read()にバッファ、サイズ、オフセットのパラメータを渡します。

両者の違い

オフセットをどこから取ってくるかが違います。

  • lseek: オフセットをファイルディスクリプタに設定
  • read: ファイルディスクリプタに設定されたオフセットをreadの実装に渡す
  • pread: システムコールに渡されたオフセットをreadの実装に渡す

もしlseekが実装されていないファイルの場合、lseekはオフセットをファイルディスクリプタに設定できません。readはファイルディスクリプタを参照してreadの実装に渡すので、オフセットは0スタート(= ファイル先頭から読み出し)です。

一方のpreadはファイルディスクリプタを無視してシステムコールに渡されたオフセットをそのままreadの実装に渡すので、lseekがあろうがなかろうが任意のオフセットから読み出せます。

編集者:すずき(2025/12/21 00:11)

コメント一覧

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



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 この記事にコメントする



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月19日 >>> 2025年12月6日
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でも試して...」

最近の記事20件

  • link 23年4月10日
    すずき (12/21 00:12)
    「[Linux - まとめリンク] 目次: Linuxカーネル、ドライバ関連。Linux kernel 2.4 for ARMが...」
  • link 25年12月19日
    すずき (12/21 00:11)
    「[preadとlseek + readは何が違う?] 目次: Linux前回(2025年12月18日の日記参照)はpreadと...」
  • link 25年12月8日
    すずき (12/20 21:48)
    「[LXPanelのボタン入れ替えが使えないときの直し方] 目次: LinuxLXDEにはLXPanelといってタスクバーやスタ...」
  • 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 22年4月13日
    すずき (12/19 10:49)
    「[C言語とlibc - まとめリンク] 目次: C言語とlibcC言語について。C++言語もたまに。プログラムの落とし穴、演算...」
  • link 16年1月25日
    すずき (12/19 10:48)
    「[紆余曲折だったC++11のoverrideとfinal] 目次: C言語とlibc最近cpprefjp(リンクはこちら)のコ...」
  • link 16年1月8日
    すずき (12/19 10:48)
    「[C, C++の可変引数マクロでのつまづきとGNU拡張構文] 目次: C言語とlibcC99, C++11の可変引数マクロでは...」
  • link 13年8月11日
    すずき (12/19 10:47)
    「[C++とPythonのクラスと動的型付け] 目次: C言語とlibc初めて触れたオブジェクト指向言語がC++で、その次がJa...」
  • link 23年9月11日
    すずき (12/19 10:42)
    「[Windows - まとめリンク] 目次: WindowsWindows XPのブリッジ機能colinuxとWindowsの...」
  • link 08年9月10日
    すずき (12/19 10:42)
    「[Windows PCの容量が足りません] 目次: Windows最近Windowsの入っているパーティション(Cドライブ)の...」
  • link 08年9月11日
    すずき (12/19 10:41)
    「[Windows XPを再インストール] 目次: WindowsCドライブを35GBに切り直してWindows XPを再インス...」
  • link 22年8月16日
    すずき (12/19 10:39)
    「[このWindows PCはN年経過しています] 目次: Windowsゲーム用PCにWindows 10をインストールしまし...」
  • link 22年8月22日
    すずき (12/19 10:37)
    「[DDRのSPD情報からCAS Latencyを取得する] 目次: LinuxDDRメモリモジュールにはSPD (Serial...」
  • link 22年3月18日
    すずき (12/18 00:31)
    「[射的 - まとめリンク] 目次: 射的ガスガン その1ガスガン その2ガスガンが増えました射的射的2回目射的3回目東京マルイ...」
  • link 25年11月30日
    すずき (12/18 00:19)
    「[JTSA Limited大会参加2025] 目次: 射的JTSA Limitedの大会に参加しました。記録は自己ベストが66...」
  • link 25年12月14日
    すずき (12/17 23:58)
    「[エアガンの外部ソース化キットを一部購入] 目次: 射的アキバのターゲット1にて減圧レギュレータ(0.4MPa)とホース、コネ...」
  • link 25年11月28日
    すずき (12/04 02:17)
    「[ジャガーさんの所感] 目次: 車ジャガーXE Sを購入してから4か月が経ちました。通勤で毎日乗っているためか走行距離が3,0...」
  • link 25年11月29日
    すずき (12/02 01:15)
    「[バーベキュー@つくば] 筑波大関連の人が集まってバーベキューするイベントが年1回開かれていて、誘ってもらったので去年から参加...」
  • link 23年5月15日
    すずき (12/02 00:43)
    「[車 - まとめリンク] 目次: 車三菱 FTO GPX '95の話。群馬県へのドライブ1群馬県へのドライブ2将来車を買い替え...」
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/21 00:12