目次: 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) のメモリにストアできない(ストアアクセスフォルトが発生)ためです。
目次: 車
三菱FTOの話。
スバルレガシィの話。
目次: ゲーム
Steamは良くできていますが、コントローラの設定は挙動がおかしくて困っています。
ゲームの右側にある [ギアのマーク] - [プロパティ] の順で選ぶと下記のウインドウが出ます。
左の [コントローラ] - [コントローラ一般設定](下線が引いてある白い文字)を選ぶと青いウインドウが出るので、検出されたコントローラ: [設定したいコントローラ] - [レイアウトの決定] を選びます。
するとキーの割り当て画面が出てくるので設定するんですが……罠が結構あります。
私の持っているELECOM JC-U3808Tというコントローラは、いわゆるスーパーファミコンのコントローラとほぼ同じ形です。つまり、十字キー, L, R, A, B, X, Y, START, SELECTボタンがあります。
しかしSteam的にはコントローラ = Xboxコントローラらしくて、上下左右は「左スティック」であり「十字キー」ではありません。
Steamは左スティックに何か設定しないと設定画面を終了できない仕様なので、間違えてしまっても気づけるはずです。たぶん。
私の環境に特有なのか、みな同じなのかわからないのですが、せっかくコントローラのボタン配置を設定してもすぐに設定が消えてデフォルトに戻ってしまいます。困りました。
Steamのコンフィグファイルを直接書き換えて、コントローラの設定を書き換えるとうまくいくことがわかりました。
SteamのコンフィグファイルはSteamのインストールディレクトリの下のconfig/config.vdfにあり、コントローラの設定はSDL_GamepadBindで検索すると発見できます。
私のゲームパッド(ELECOM JC-U3808T)の場合は、一度設定を全部削除して下記のOKの設定だけを書くとうまくいきました。
03000000790000001100000000000000,dev:none:USB Gamepad,platform:Windows,a:b2,b:b3,y:b1,x:b0,start:b7,back:b6,leftshoulder:b4,rightshoulder:b5,dpright:a3,leftx:a0,lefty:a4,
これも罠があります。コントローラー設定を削除したあとSteamを起動すると、下記のようなRetrolink SNES Controllerというそれっぽい設定名のコンフィグが生成されます。この設定を書き換えれば良いのか?と思いきや、いくら書き換えても設定が反映されません。なんだこれ??
03000000790000001100000000000000,Retrolink SNES Controller,platform:Windows,a:b2,b:b3,y:b1,x:b0,start:b7,back:b6,leftshoulder:b4,rightshoulder:b5,dpright:a3,leftx:a0,lefty:a4,
OKとNGを比較してもデバイス名くらいしか違いがないのですが、一体何が気に食わないんでしょう?Steamのコントローラ回りは変な動きで理解し難いです。
< | 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 | - | - | - | - | - | - |
合計:
本日: