コグノスケ


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

link もっと前
2023年5月7日 >>> 2023年4月28日
link もっと後

2023年4月28日

Linuxの /dev/zeroの実装

目次: Linux

Twitterで/dev/zeroの話をしている人が居て、そういえばLinuxはどこで実装しているのかうろ覚えだったので、コードを調べつつメモしておきます。

デバイスメジャー番号の調べ方

デバイスファイル /dev/zeroをls -lで見ると、キャラクターデバイスであること、メジャー番号1、マイナー番号5だと分かります。

/dev/zeroをlsで調べた結果
$ LANG=C ls -la /dev/zero

crw-rw-rw- 1 root root 1, 5 Mar 27 23:22 /dev/zero
↑                    ↑
↑                    デバイスメジャー番号1、マイナー番号5
c: キャラクターデバイス

Linuxの場合/proc/devicesにデバイスメジャー番号の一覧があります。

/proc/devicesの中身
$ head /proc/devices

Character devices:
  1 mem    ★★キャラクターデバイス、メジャー番号1 = memドライバ★★
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc

メジャー番号1を使用しているmemというドライバを調べれば良いことが分かりました。

ドライバの実装箇所

大抵はドライバ名でgrepすると発見できますが、memという単語はいたるところに使われていて探しにくいと思います。幸いながらmemはメジャー番号が常に1に固定されたドライバなので、メジャー番号を頼りに探した方が良いでしょう。

メジャー番号1を使用するドライバ

// linux/include/uapi/linux/major.h

#define MEM_MAJOR		1


// linux/drivers/char/mem.c

static int __init chr_dev_init(void)
{
	int minor;

	if (register_chrdev(MEM_MAJOR, "mem", &memory_fops))    //★★キャラクターデバイス、メジャー番号1 = memとして登録★★
		printk("unable to get major %d for memory devs\n", MEM_MAJOR);

メジャー番号の謎は解けて、実装箇所がdrivers/char/mem.cであることがわかりました。

マイナー番号の役割

次に気になるのは/dev/zeroも /dev/nullも同じメジャー番号1ですが、どう区別するか?です。メジャー番号は同じでも、マイナー番号は異なっていて /dev/zero = 5, /dev/null = 3です。マイナー番号をどこで見ているか、何に使っているか調べます。

open関数とマイナー番号

// linux/drivers/char/mem.c

static const struct file_operations memory_fops = {
	.open = memory_open,    //★★メジャー番号1のキャラクタデバイスファイルを開く時に呼ばれる関数★★
	.llseek = noop_llseek,
};

static int memory_open(struct inode *inode, struct file *filp)
{
	int minor;
	const struct memdev *dev;

	minor = iminor(inode);
	if (minor >= ARRAY_SIZE(devlist))
		return -ENXIO;

	dev = &devlist[minor];    //★★デバイスマイナー番号をみて操作を決める★★
	if (!dev->fops)
		return -ENXIO;

	filp->f_op = dev->fops;
	filp->f_mode |= dev->fmode;

	if (dev->fops->open)
		return dev->fops->open(inode, filp);

	return 0;
}

マイナー番号からdevlistの何番目の要素を使うか決めているようです。devlistは何かというと、

memドライバのdevlist変数の定義

// linux/drivers/char/mem.c

static const struct memdev {
	const char *name;
	umode_t mode;
	const struct file_operations *fops;
	fmode_t fmode;
} devlist[] = {
#ifdef CONFIG_DEVMEM
	 [DEVMEM_MINOR] = { "mem", 0, &mem_fops, FMODE_UNSIGNED_OFFSET },
#endif
	 [3] = { "null", 0666, &null_fops, FMODE_NOWAIT },
#ifdef CONFIG_DEVPORT
	 [4] = { "port", 0, &port_fops, 0 },
#endif
	 [5] = { "zero", 0666, &zero_fops, FMODE_NOWAIT },     //★★minor 5 = /dev/zero★★
	 [7] = { "full", 0666, &full_fops, 0 },
	 [8] = { "random", 0666, &random_fops, FMODE_NOWAIT },
	 [9] = { "urandom", 0666, &urandom_fops, FMODE_NOWAIT },
#ifdef CONFIG_PRINTK
	[11] = { "kmsg", 0644, &kmsg_fops, 0 },
#endif
};

...

static const struct file_operations zero_fops = {
	.llseek		= zero_lseek,
	.write		= write_zero,
	.read_iter	= read_iter_zero,
	.read		= read_zero,    //★★read関数★★
	.write_iter	= write_iter_zero,
	.mmap		= mmap_zero,
	.get_unmapped_area = get_unmapped_area_zero,
#ifndef CONFIG_MMU
	.mmap_capabilities = zero_mmap_capabilities,
#endif

マイナー番号をインデックスとした様々なデバイスを定義しています。今回はインデックス5, "zero" (= /dev/zero) をさらに追います。

/dev/zeroの正体

ゼロを返すとはすなわち、読み出す(= readを呼ぶ)とバッファの中身にゼロが書かれて戻ってくることを意味します。/dev/zeroのread関数であるread_zero() を見ると、

/dev/zeroのread関数

// linux/drivers/char/mem.c

static ssize_t read_zero(struct file *file, char __user *buf,
			 size_t count, loff_t *ppos)
{
	size_t cleared = 0;

	while (count) {
		size_t chunk = min_t(size_t, count, PAGE_SIZE);
		size_t left;

		left = clear_user(buf + cleared, chunk);    //★★バッファにゼロを書いている★★
		if (unlikely(left)) {
			cleared += (chunk - left);
			if (!cleared)
				return -EFAULT;
			break;
		}
		cleared += chunk;
		count -= chunk;

		if (signal_pending(current))
			break;
		cond_resched();
	}

	return cleared;
}

バッファをゼロクリアしているのはclear_user() という関数のようです。この関数はアーキテクチャによって処理が異なります。一例としてRISC-Vの実装を見ましょう。

clear_user() 関数RISC-V向けの実装

// linux/arch/riscv/include/asm/uaccess.h

static inline
unsigned long __must_check clear_user(void __user *to, unsigned long n)
{
	might_fault();
	return access_ok(to, n) ?
		__clear_user(to, n) : n;
}


// linux/arch/riscv/lib/uaccess.S

	.macro fixup op reg addr lbl
100:
	\op \reg, \addr
	_asm_extable	100b, \lbl
	.endm


ENTRY(__clear_user)

	/* Enable access to user memory */
	li t6, SR_SUM
	csrs CSR_STATUS, t6    //★SUM (permit Supervisor User Memory access) をセットしている★
                               //★詳細はriscv-privilegedの3.1.6.3 Memory Privilege in mstatus Registerを参照★

	add a3, a0, a1
	addi t0, a0, SZREG-1
	andi t1, a3, ~(SZREG-1)
	andi t0, t0, ~(SZREG-1)
	/*
	 * a3: terminal address of target region
	 * t0: lowest doubleword-aligned address in target region
	 * t1: highest doubleword-aligned address in target region
	 */
	bgeu t0, t1, 2f
	bltu a0, t0, 4f
1:
	fixup REG_S, zero, (a0), 11f    //★★メインのループ処理★★
	addi a0, a0, SZREG
	bltu a0, t1, 1b
2:
	bltu a0, a3, 5f

3:
	/* Disable access to user memory */
	csrc CSR_STATUS, t6
	li a0, 0
	ret
4: /* Edge case: unalignment */
	fixup sb, zero, (a0), 11f
	addi a0, a0, 1
	bltu a0, t0, 4b
	j 1b
5: /* Edge case: remainder */
	fixup sb, zero, (a0), 11f
	addi a0, a0, 1
	bltu a0, a3, 5b
	j 3b

	/* Exception fixup code */
11:
	/* Disable access to user memory */
	csrc CSR_STATUS, t6
	mv a0, a1
	ret
ENDPROC(__clear_user)
EXPORT_SYMBOL(__clear_user)


// linux/arch/riscv/include/asm/asm.h

#define REG_S		__REG_SEL(sd, sw)    //★64bitならsd, 32bitならsw★


//★アセンブラの場合aがそのまま出力、Cの場合は文字列として出力★
//  asm: __ASM_STR(abc) -> aaa
//  C: __ASM_STR(abc) -> "aaa"
#if __riscv_xlen == 64
#define __REG_SEL(a, b)	__ASM_STR(a)
#elif __riscv_xlen == 32
#define __REG_SEL(a, b)	__ASM_STR(b)
#else
#error "Unexpected __riscv_xlen"
#endif


#ifdef __ASSEMBLY__
#define __ASM_STR(x)	x    //★アセンブラなのでこちら★
#else
#define __ASM_STR(x)	#x
#endif

アセンブラが出てきて面喰らいますが、基本的にはバッファにゼロをストアする処理です。memset() と大きく異なる点はmstatus.SUMをセットしていることです。RISC-VではSUMをセットしないとS-mode (Supervisor mode) からU-mode (User mode) のメモリにストアできない(ストアアクセスフォルトが発生)ためです。

編集者:すずき(2023/04/29 23:28)

コメント一覧

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



link もっと前
2023年5月7日 >>> 2023年4月28日
link もっと後

管理用メニュー

link 記事を新規作成

<2023>
<<<05>>>
-123456
78910111213
14151617181920
21222324252627
28293031---

最近のコメント5件

  • link 21年3月13日
    すずきさん (03/05 15:13)
    「あー、このプログラムがまずいんですね。ご...」
  • link 21年3月13日
    emkさん (03/05 12:44)
    「キャストでvolatileを外してアクセ...」
  • link 24年1月24日
    すずきさん (02/19 18:37)
    「簡単にできる方法はPowerShellの...」
  • link 24年1月24日
    KKKさん (02/19 02:30)
    「追伸です。\nネットで調べたらマイクロソ...」
  • link 24年1月24日
    KKKさん (02/19 02:25)
    「私もエラーで困ってます\n手動での回復パ...」

最近の記事3件

  • link 24年3月25日
    すずき (03/26 03:20)
    「[Might and Magic Book One TASのその後] 目次: Might and Magicファミコン版以前(...」
  • link 21年10月4日
    すずき (03/26 03:14)
    「[Might and Magicファミコン版 - まとめリンク] 目次: Might and Magicファミコン版TASに挑...」
  • link 24年3月19日
    すずき (03/20 02:52)
    「[モジュラージャックの規格] 古くは電話線で、今だとEthernetで良く見かけるモジュラージャックというコネクタとレセプタク...」
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 過去日記について

その他の情報

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

合計:  counter total
本日:  counter today

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

最終更新: 03/26 03:20