目次: Linux
Twitterで/dev/zeroの話をしている人が居て、そういえばLinuxはどこで実装しているのかうろ覚えだったので、コードを調べつつメモしておきます。
デバイスファイル/dev/zeroをls -lで見ると、キャラクターデバイスであること、メジャー番号1、マイナー番号5だと分かります。
$ 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にデバイスメジャー番号の一覧があります。
$ 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に固定されたドライバなので、メジャー番号を頼りに探した方が良いでしょう。
// 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です。マイナー番号をどこで見ているか、何に使っているか調べます。
// 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は何かというと、
// 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) をさらに追います。
ゼロを返すとはすなわち、読み出す(= readを呼ぶ)とバッファの中身にゼロが書かれて戻ってくることを意味します。/dev/zeroのread関数であるread_zero() を見ると、
// 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の実装を見ましょう。
// 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) のメモリにストアできない(ストアアクセスフォルトが発生)ためです。
この記事にコメントする
目次: RISC-V
いつもArty A7-100を書き換えるときSPI Flashの型番を忘れるので、手順と一緒にメモしておきます。
もしLinuxを使っている場合は、Vivadoをインストールしたディレクトリの下にudev用ルールがありますので、/etc/udev/rules.d/にコピーします。
$ cp \ Vivado/2023.2/data/xicom/cable_drivers/lin64/install_script/install_drivers/52-xilinx-ftdi-usb.rules \ Vivado/2023.2/data/xicom/cable_drivers/lin64/install_script/install_drivers/52-xilinx-pcusb.rules \ Vivado/2023.2/data/xicom/cable_drivers/lin64/install_script/install_drivers/52-xilinx-digilent-usb.rules \ /etc/udev/rules.d/ # コピーした後はリロード $ sudo udevadm control -R
Arty A7にはXilinx Artix-7 (XC7A100TCSG324-1) というFPGAが搭載されています。XilinxのFPGAを扱うにはVivadoというツールが必要で、このツールを使うためには有料ライセンスが必要です。が、FPGAに回路を書き込むだけなら無料で使用可能なLab Editionが使えます。起動したらOpen Hardware Managerを選択します。
Open TargetとAuto Connectを選択します。PCにボードを複数接続している場合は、Arty A7が選ばれるとは限らないので、Open New Targetを選択してウィザードを進める必要があるかもしれません。
通常FPGAの回路はFPGA内のRAMに書かれるため、揮発性つまり電源を切ると消えます。しかしConfiguration用のSPI Flashに回路を書くと、次回以降のボード電源投入時にFPGAがSPI Flashから自動的に回路をロードしてくれる仕組みです。
Configuration用のSPI Flashに書き込むには、Vivadoに対してボード上のSPI Flash品番を教える必要があります。Add Configuration Memory Deviceを選択しましょう。

Add Configuration Memory Device
Arty A7のSchematics(回路図)を見るとInfineon TechnologiesのS25FL128Sという型番のSPI Flashが搭載されているようです。VivadoのSearchボックスに型番を途中まで入れると、それらしい選択肢が表示されます。
Configuration Memory Deviceを追加出来たら、Program Configuration Memory Deviceを選択します。ダイアログに回路データ(*.mcs)ファイル名を入れて、FPGAの回路をボード上のSPI Flashに書き込みます。
書き込みには数分掛かるはずです。回路データを書き込んだら、ボードの電源を一度切って再投入しましょう。書き込んだ回路データが反映されるはずです。
この記事にコメントする
目次: 車
もはや毎年恒例レベルですが、車のバッテリーが完全に干上がって死にました。
電圧は1.5Vでした。乾電池か?
自宅の駐車場でバッテリー上がり、などというくだらない理由で幾度となくJAFを呼ぶのがいい加減恥ずかしくなってきたので、先日KashimuraのジャンプスターターKD-238(メーカーサイトへのリンク)を買いました。
ジャンプスターターは車がないと無意味な代物ですが、その点KD-238はモバイルバッテリーとしても使えるので安心です。普通のモバイルバッテリーと比べるとゴツいですが、そこはご愛敬で。
早速買った後にジャンプスタートを試したんですけど、エンジン掛からなかったんですよ……。説明書に「ジャンプスタートは一度に2回までにしておけ」とあったので、素直に従ってその日は諦めました。
今日もダメ元で、ジャンプスタートにチャレンジしてみたところ、あっさりエンジンが掛かりました。キーを回す前にアクセルをバシバシ連打したのが良かったんでしょうか?先日も今日もECUのエラーなどは出ていなかったため、原因は皆目見当が付きません。不思議ですね??
エンジン始動直後を見ると、電圧は14V超(充電しているときの電圧)、クランプメーターで電流を見ると2.6Aで、一応バッテリーは充電されているようですが、充電がわずか5分くらいで止まってしまいます。これはダメそう。
さらにバッテリー端子の周りを良く見ると、白い粉を吹いています。雨で濡れた?ような跡にも見えますが、バッテリーの上面にはカバーがあるので雨は掛からないはずです。
これはまさか過放電の影響か何かでバッテリー液漏れしたんでしょうか?もう色々ダメですね。諦めてバッテリー交換か……あーあ。
ちょうど今日はスバルのディーラーで半年点検する日だったので、ディーラーに何とかしてもらうために走り始めました。走っている最中にも関わらずバッテリー電圧は非常に不安定で、12Vを突然下回るなど挙動不審です。交差点で止まるとエンジンの回転数が落ちてガタガタとエンストしかけていました。環八でエンストとかやめて、怖い。
当然ながら、ディーラー整備士のお兄さんに「バッテリーがかなり消耗しています、交換をお勧めします」と言われました。ソウデスネ……。素直にバッテリー交換してもらい26,000円くらいの出費でした。痛え出費です。
メモにあるだけでも 2007年11月17日、2013年3月20日、2016年7月24日、2020年7月28日、2022年1月10日にバッテリー交換しています。これで6回目です。バッテリーだけで多分20万円くらい無駄にしています。
私は生活サイクル的には、明日いきなり車がなくなっても全く困りません……。でも近所の駐車場は車がたくさん停まっている=つまり東京で車を持って暮らしている人は意外といるようです。みなさん一体何に使っているんでしょうね……??
この記事にコメントする
| < | 2023 | > | ||||
| << | < | 04 | > | >> | ||
| 日 | 月 | 火 | 水 | 木 | 金 | 土 |
| - | - | - | - | - | - | 1 |
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | - | - | - | - | - | - |
25年10月6日
25年10月6日
25年9月29日
25年9月29日
20年8月24日
20年8月24日
16年2月14日
16年2月14日
25年7月20日
25年7月20日
25年7月20日
25年7月20日
25年7月20日
25年7月20日
20年8月16日
20年8月16日
20年8月16日
20年8月16日
24年6月17日
24年6月17日
wiki
Linux JM
Java API
2002年
2003年
2004年
2005年
2006年
2007年
2008年
2009年
2010年
2011年
2012年
2013年
2014年
2015年
2016年
2017年
2018年
2019年
2020年
2021年
2022年
2023年
2024年
2025年
過去日記について
アクセス統計
サーバ一覧
サイトの情報合計:
本日: