èªãã æ¬

èè : å¹³ç° è±
Kernel Maintainers Summit 2025ã§Linux kernelãžã®Rustå°å ¥ã«ã€ããŠã"experimental" ãªæ±ããçµããŠããã®ã§ã¯ãšããææ¡ããããŸããã
Ojeda returned to his initial question: can the "experimental" status be ended? Torvalds said that, after nearly five years, the time had come.
The state of the kernel Rust experiment
ãã®åŸãRust subsystemã®maintainerã§ãããã Miguel Ojedaæ°ã«ãã[PATCH] rust: conclude the Rust experimentã§
But the experiment is done, i.e. Rust is here to stay.
ãšããŠãDocumentation/rust/index.rstããã The Rust experiment sectionãåé€ãã倿Žãææ¡ãããŸããã
ãã®ãããªRust for Linuxã®åãçµã¿ãæ©ã«Linux kernelã«ã€ããŠèª¿ã¹ãŠã¿ãããšæãããã«ãªããŸããã Rustã®ãŠãŒã¹ã±ãŒã¹ãšããŠãdriverãrustã§æžãåãçµã¿ãããããšãç¥ããkernel driverã®è§£èª¬ãæ¢ããŠããäžã§æ¬æžãèŠã€ããèªãã§ã¿ãŠãããããã£ãã®ã§ãææ³ãæžããŸãã
æ¬æžã察象ãšããŠããkernel versionã¯2.6.23.1ã§ãããèšäºäžã®ã³ãŒãã¯6.18.6ã§åãããŸããã
第1ç« Linuxããã€ã¹ãã©ã€ãã®æŠèŠ
Linux kernelã®çãç«ã¡ããã³ãã¥ããã£ã«ã€ããŠã ãŸããããã€ã¹ãã©ã€ãã®kernelå ã«ãããäœçœ®ã¥ãã«ã€ããŠã
第2ç« Linuxã®ã©ã€ã»ã³ã¹
GPLã©ã€ã»ã³ã¹ãããã€ã¹ãã©ã€ãã®ã©ã€ã»ã³ã¹ã«ã€ããŠã kernel moduleã®MODULE_LICENSE macroã®èª¬æããããŸãã éå»ã®GPLéåã®äºäŸçã玹ä»ãããŠããŸããã
第3ç« ããã€ã¹ãã©ã€ãéçºã®æºå
å®éã«ãã©ã€ããæžãããã®ç°å¢æ§ç¯æ¹æ³ã«ã€ããŠã èªåã¯NixOSã䜿ã£ãŠããã®ã§ä»¥äžã®ãããªNixOS moduleãæºåããŸããã
{ pkgs, config, ... }:
let
kernel = config.boot.kernelPackages.kernel;
in
{
environment.systemPackages = with pkgs; [
kernel.dev
];
environment.variables.KDIR = "${kernel.dev}/lib/modules/${kernel.modDirVersion}/build";
}ãããããšå®è¡äžã®kernel versionã«å¯Ÿå¿ãããmodule buildçšã®fileãæãã®ã§ãkernelèªäœãbuildããã«æžã¿ãŸãã
cd $env.KDIR
pwd
/nix/store/cbc470bffdhc9wg1bgk54i6a3s5a1ygq-linux-6.18.6-dev/lib/modules/6.18.6/build
uname | get kernel-release
6.18.6
ls
# | name | type | size | modified
---+----------------+---------+--------+--------------
0 | Makefile | file | 350 B | 56 years ago
1 | Module.symvers | file | 2.3 MB | 56 years ago
2 | arch | dir | 4.0 kB | 56 years ago
3 | include | dir | 4.0 kB | 56 years ago
4 | kernel | dir | 4.0 kB | 56 years ago
5 | rust | dir | 4.0 kB | 56 years ago
6 | scripts | dir | 4.0 kB | 56 years ago
7 | source | symlink | 9 B | 56 years ago
8 | tools | dir | 4.0 kB | 56 years ago 第4ç« ããã€ã¹ãã©ã€ãéçºã®ç¬¬äžæ©
æ¬ç« ãããããããã©ã€ãéçºãã¯ããŸããŸãã ãã°åºåã ããè¡ããã©ã€ããhello
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
pr_info("hello: loaded\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("hello: unloaded\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ymgyt");
MODULE_DESCRIPTION("hello module");Makefile
obj-m := hello.o
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) cleanmake modulesã§buildããŠã¿ããš
make modules
/nix/store/mkm3my2067305hdh7rzmi10npwr7y17f-gnumake-4.4.1/bin/make -C /nix/store/cbc470bffdhc9wg1bgk54i6a3s5a1ygq-linux-6.18.6-dev/lib/modules/6.18.6/build M=/home/ymgyt/rs/drivers/hello modules
make[1]: Entering directory '/nix/store/cbc470bffdhc9wg1bgk54i6a3s5a1ygq-linux-6.18.6-dev/lib/modules/6.18.6/build'
make[2]: Entering directory '/home/ymgyt/rs/drivers/hello'
CC [M] hello.o
MODPOST Module.symvers
CC [M] hello.mod.o
CC [M] .module-common.o
LD [M] hello.ko
BTF [M] hello.ko
Skipping BTF generation for hello.ko due to unavailability of vmlinux
make[2]: Leaving directory '/home/ymgyt/rs/drivers/hello'
make[1]: Leaving directory '/nix/store/cbc470bffdhc9wg1bgk54i6a3s5a1ygq-linux-6.18.6-dev/lib/modules/6.18.6/build'
ls
# | name | type | size | modified
---+----------------+------+----------+----------------
0 | Makefile | file | 262 B | 3 weeks ago
1 | Module.symvers | file | 0 B | 19 seconds ago
2 | hello.c | file | 342 B | 3 weeks ago
3 | hello.ko | file | 179.6 kB | 18 seconds ago
4 | hello.mod | file | 10 B | 19 seconds ago
5 | hello.mod.c | file | 373 B | 19 seconds ago
6 | hello.mod.o | file | 106.5 kB | 18 seconds ago
7 | hello.o | file | 14.0 kB | 19 seconds ago
8 | modules.order | file | 8 B | 19 seconds ago
file hello.ko
hello.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=e328d799fd4e7da7538d9b81b324aac66b6ca788, with debug_info, not stripped ç¡äºãhello.ko ELFãçæã§ããŸããã
modinfoããŠã¿ããšãMODULE_ ãã¯ãã®å
容ãåæ ããŠããããšã確ãããããŸã
modinfo hello.ko
filename: /home/ymgyt/rs/drivers/hello/hello.ko
description: hello module
author: ymgyt
license: GPL
depends:
name: hello
retpoline: Y
vermagic: 6.18.6 SMP preempt mod_unload ã¡ãªã¿ã«rustã ãšãkernel crateã«driverã®çš®å¥ããšã«macroãçšæããŠããã以äžã®ããã«ããŠmoduleã®metadataãå®çŸ©ã§ããŸãã
kernel::module_usb_driver! {
type: SampleDriver,
name: "rust_driver_usb",
authors: ["Daniel Almeida"],
description: "Rust USB driver sample",
license: "GPL v2",
}äœæãããã©ã€ããããŒãããã«ã¯ãinsmodãå®è¡ããŸãã
sudo insmod hello.ko
sudo dmesg | tail -n 10 | rg hello
[883897.532805] hello: loadedç¡äºãã°ã確èªã§ããŸããã
ããŒããããã©ã€ããã¢ã³ããŒãããã«ã¯rmmodã䜿ããŸãã
sudo rmmod hello
sudo dmesg | tail -n 10 | rg hello
[883897.532805] hello: loaded
[883981.712000] hello: unloadedãšããããšã§ãã©ã€ãã®ãããŒã¯ãŒã«ããã§ããŸããã æ¬¡ã«ã颿°ãå®çŸ©ããŠããheaderã«jumpã§ãããšäŸ¿å©ãªã®ã§code jumpã®èšå®ãè¡ããŸãã èªåã¯clangd lang serverãå©çšããŠããã®ã§
bear --append --output -- make -C $KDIR M=$(pwd) HOSTCC=cc modulesãå®è¡ããŠãcompile_commands.jsonãçæããŸããã ã¡ãªã¿ã«Rustã®å Žåã¯ãkernelã®make targetã«rust-analyzerããããŸãã
make help | rg rust-analyzer
rust-analyzer - Generate rust-project.json rust-analyzer support file次ã«ãšã³ããªãŒãã€ã³ãã«ã€ããŠã®è§£èª¬ããããŸãã insmodæã«åŒã°ãããmodule_init, rmmodæã®module_exitã ãã§ãªããsystemcall(open, close, ioctl, epoll, ...)æã®handler, å²èŸŒã¿,timerããããã€ã¹ã®åŠçãåŒã°ããŸãã
第5ç« ãã©ã€ãããã°ã©ãã³ã°ã®åºç€ç¥è
ã³ã³ããã¹ãã®æŠå¿µã説æãããŸãã ããã»ã¹èµ·å ã§åŒã°ããããå²èŸŒã¿èµ·å ã§åŒã°ãããã«ãã£ãŠãããã»ã¹ã³ã³ããã¹ããšå²èŸŒã¿ã³ã³ããã¹ãã«åé¡ãããŸãã
ããã»ã¹ã³ã³ããã¹ãã§ã¯ãglobal 倿°ãcurrent ããçŸåšå®è¡äžã®ã¹ã¬ããã衚ãtask_structã®åç
§ãåŸãããŸãã
ãã©ã€ãç¹æã®è©±ã§ã¯ãªãã§ãããintãlongãšãã£ãããŒã¿ã¢ãã«ããšã³ãã£ã¢ã³ãã¢ã©ã€ã³ã¡ã³ãã®è§£èª¬ããããŸãã
ãããŠãæ¬ç« ã§ã¯readãããš1ãè¿ããdevoneãã©ã€ããäœã£ãŠãããŸãã
hexyl -n 32 -v /dev/devone0
ââââââââââ¬ââââââââââââââââââââââââââ¬ââââââââââââââââââââââââââ¬âââââââââ¬âââââââââ
â00000000â ff ff ff ff ff ff ff ff â ff ff ff ff ff ff ff ff âÃÃÃÃÃÃÃÃâÃÃÃÃÃÃÃÃâ
â00000010â ff ff ff ff ff ff ff ff â ff ff ff ff ff ff ff ff âÃÃÃÃÃÃÃÃâÃÃÃÃÃÃÃÃâ
ââââââââââŽââââââââââââââââââââââââââŽââââââââââââââââââââââââââŽâââââââââŽâââââââââãã£ããinitåŠçããã¿ãŠãããŸãã
static int devone_major = 0; /* dynamic allocation */
static int devone_minor = 0; /* static allocation */
static int devone_devs = 1;
static int __init devone_init(void)
{
dev_t dev = MKDEV(devone_major, devone_minor);
int ret;
ret = alloc_chrdev_region(&dev, 0, devone_devs, DRIVER_NAME);
if (ret)
return ret;
devone_major = MAJOR(dev);
cdev_init(&devone_cdev, &devone_fops);
devone_cdev.owner = THIS_MODULE;
devone_cdev.ops = &devone_fops;
devone_class = class_create(DRIVER_NAME);
if (IS_ERR(devone_class)) {
ret = PTR_ERR(devone_class);
devone_class = NULL;
goto err_unregister;
}
devone_device =
device_create(devone_class, NULL, dev, NULL, "devone%d", devone_minor);
if (IS_ERR(devone_device)) {
ret = PTR_ERR(devone_device);
devone_device = NULL;
goto err_class;
}
ret = cdev_add(&devone_cdev, dev, devone_devs);
if (ret)
goto err_device;
printk(KERN_ALERT "%s: driver(major %d) installed\n", DRIVER_NAME,
devone_major);
return 0;
err_device:
device_destroy(devone_class, dev);
devone_device = NULL;
err_class:
class_destroy(devone_class);
devone_class = NULL;
err_unregister:
unregister_chrdev_region(dev, devone_devs);
return ret;
}åçš®é¢æ°ãåŒã¶ãšãªã«ãèµ·ããããäžå¯§ã«è§£èª¬ãããŠããã®ã§ãæ¬æžãèªããšãã®ã³ãŒããèªããããã«ãªããŸãã æ¬æžã§ã¯ãclass_device_create()颿°ã䜿ãããŠããã®ã§ãããä»ã¯åé€ãããŠããã代ããã«device_create()ãå©çšããŸããã
pointerãè¿ã颿°ã§ã¯ããšã©ãŒã®æ
å ±ãåãpointeråã§è¡šçŸããã®ã§ãå€å®ããã³ããšã©ãŒæ
å ±ãžã®å€æçšã®helper颿°ãIS_ERR(), PTR_ERR()ãçšæãããŠããŸãããã©ã€ããŒã«éããªãã¢ãžã¥ãŒã«äžè¬ã®è©±ã«ã€ããŠã¯ãLinuxã«ãŒãã«ããã°ã©ãã³ã°ç¬¬2çã®èª¬æãããããããã£ãã§ãã
ãªãœãŒã¹ã®å²åœãšåæåãé çªã«è¡ã£ãŠããã®ã§ããšã©ãŒãçºçããå Žåã¯ãªãœãŒã¹ã®éæŸãè¡ãå¿ èŠããããŸããããããåŠçã¯gotoã®ã»ããæžããããããšãããããgotoãããªãã¡æªããã®ã§ã¯ãªããã ãªãšæããŸããã(èªåã§ã¯çµ¶å¯Ÿãã¹ãããã§ãã)
ãããŠã以äžã®udev ruleãé©çšããŠãããinsmod()ãããšã/dev/devone0fileãopen()ã§ããããã«ãªããŸãã
cat <<EOF | sudo tee /run/udev/rules.d/51-devone.rules > /dev/null
KERNEL=="devone[0-9]*", GROUP="root", MODE="0644"
EOF
sudo insmod ./devone.ko
file /dev/devone0
/dev/devone0: character special (236/0)次ã«openåŠçãå®è£ ããŸãã
#include <linux/fs.h>
static int devone_open(struct inode *inode, struct file *file)
{
struct devone_data *p;
p = kmalloc(sizeof(struct devone_data), GFP_KERNEL);
if (p == NULL) {
printk("%s: No memory\n", __func__);
return -ENOMEM;
}
p->val = 0xff;
rwlock_init(&p->lock);
file->private_data = p;
return 0;
}åŒæ°ã«openãããfileã®inodeãšfile handlerãæž¡ãããŸãã å人çã«ã¯åããŠãinodeã®å®çŸ©ãèªãã ã®ã§ããããä»ãŸã§inode,inodeãã£ãŠããå®äœããšãªããŸããã
struct file {
void *private_data;
/* ... */
}fileã«ã¯void *ãä¿æã§ããprivate_data ããããããã«ãã©ã€ãåŽã§å®çŸ©ããåŠçã«é¢ããããŒã¿ãæž¡ãããšãã§ããŸããçµæ§èªç±ãªãã§ããã
struct devone_data {
unsigned char val;
rwlock_t lock;
};1ãåºå®ã§è¿ããreadåŠçã¯ä»¥äžã®ããã«ãªããŸãã
static ssize_t devone_read(struct file *filep, char __user *buf, size_t count,
loff_t *f_pos)
{
struct devone_data *p = filep->private_data;
int i;
unsigned char val;
int retval;
read_lock(&p->lock);
val = p->val;
read_unlock(&p->lock);
for (i = 0; i < count; i++) {
if (copy_to_user(&buf[i], &val, 1)) {
retval = -EFAULT;
goto out;
}
}
retval = count;
out:
return (retval);
}openæã«åæåããdevone_dataããå€(1)ãåãåºããŠããŠãŒã¶é åã«æžã蟌ã¿ãŸããreadã¯è€æ°threadããåŒã°ããã®ã§ãå
±æé åã«ã¢ã¯ã»ã¹ããå Žåã¯ããã¯ããšããŸãããã®ãããã®èãæ¹ã¯éåžžã®ããã°ã©ãã³ã°ãšåãã ãšæããŸãã(sleepã®å¯åŠãé€ããŠ)
ä»åã¯readããããŒã¿ã決ãæã¡ãªã®ã§ãåžžã«åŠçã§ããŸãããå®éã®ãã©ã€ãã§ã¯ããããããã€ã¹ãå¶åŸ¡ããåŠçãèµ°ããå Žåã«ãã£ãŠã¯ãŠãŒã¶ã«æ å ±ãè¿ããªãã§ãããã®éã«ãã©ã€ãåŽã§ãCPUã®åŠçãææŸãããšã«ãªããããã»ã¹ãã¹ãªãŒãããŸãã
writeåŠçã¯åæ§ã«ä¿æããŠããããŒã¿ã«æžã蟌ãã ãã§ã
static ssize_t devone_write(struct file *filep, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct devone_data *p = filep->private_data;
unsigned char val;
int retval = 0;
if (count >= 1) {
if (copy_from_user(&val, &buf[0], 1)) {
retval = -EFAULT;
goto out;
}
write_lock(&p->lock);
p->val = val;
write_unlock(&p->lock);
retval = count;
}
out:
return (retval);
}以äžã®åŠçã§ã¢ã¯ã»ã¹ããŠã¿ããš
use std::{
env, fs,
io::{Read as _, Write as _},
};
use tracing::info;
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let dev_file = env::var("DEV_FILE").unwrap_or("/dev/devone0".to_owned());
let mut f = fs::OpenOptions::new()
.read(true)
.write(true)
.open(dev_file)?;
let mut buf = [0; 8];
f.write_all(b"A")?;
f.read_exact(&mut buf)?;
info!("Read: {}", String::from_utf8_lossy(&buf));
f.write_all(b"B")?;
f.read_exact(&mut buf)?;
info!("Read: {}", String::from_utf8_lossy(&buf));
Ok(())
}2026-02-08T08:23:16.416004Z INFO devone: Read: AAAAAAAA
2026-02-08T08:23:17.439580Z INFO devone: Read: BBBBBBBBç¡äºä»»æã®ããŒã¿ãæžã蟌ãããšãã§ããŸããã
第6ç« ãã©ã€ãããã°ã©ãã³ã°ã®å®é
æ¬ç« ã§ã¯ãioctlã®å®è£ æ¹æ³ã®è§£èª¬ããããŸãã ioctlã§ã¯ããŠãŒã¶ã¹ããŒã¹ã®ããã°ã©ã ãšããŒã¿æ§é ãå ±æããå¿ èŠãããã®ã§ããŸãheader fileãå®çŸ©ããŸãã
devone_ioctl.h
#ifndef _DEVONE_IOCTL_H
#define _DEVONE_IOCTL_H
#include <linux/ioctl.h>
struct ioctl_cmd {
unsigned int reg;
unsigned int offset;
unsigned int val;
};
#define IOC_MAGIC 'd'
enum {
IOCTL_VALSET = _IOW(IOC_MAGIC, 1, struct ioctl_cmd),
IOCTL_VALGET = _IOR(IOC_MAGIC, 2, struct ioctl_cmd),
};
#endifioctl handler
#include "devone_ioctl.h"
static long int devone_ioctl(struct file *filep, unsigned int cmd,
unsigned long arg)
{
struct devone_data *dev = filep->private_data;
struct ioctl_cmd data;
struct ioctl_cmd __user *uarg = (struct ioctl_cmd __user *)arg;
if (_IOC_TYPE(cmd) != IOC_MAGIC)
return -ENOTTY;
if (_IOC_SIZE(cmd) != sizeof(struct ioctl_cmd))
return -EINVAL;
memset(&data, 0, sizeof(data));
switch (cmd) {
/* userland write */
case IOCTL_VALSET:
if (!capable(CAP_SYS_ADMIN)) {
return -EPERM;
}
if (copy_from_user(&data, uarg, sizeof(data))) {
return -EFAULT;
}
write_lock(&dev->lock);
dev->val = data.val;
write_unlock(&dev->lock);
break;
/* userland read */
case IOCTL_VALGET:
read_lock(&dev->lock);
data.val = dev->val;
read_unlock(&dev->lock);
if (copy_to_user(uarg, &data, sizeof(data))) {
return -EFAULT;
}
break;
default:
return -ENOTTY;
break;
}
return 0;
}ãã£ããã®ioctlã§ãããã£ãŠããããšã¯ãread/writeã§ãã å人çã«ã¯ã
if (!capable(CAP_SYS_ADMIN)) {
return -EPERM;
}ã®ããã«ãcapabilityã®ãã§ãã¯ããã©ã€ãã®ifã§å®è£ ã§ãããšããããšãç¥ããããããã£ãã§ãã
ãã®ioctlãããã°ã©ã ããèªãã§ã¿ãŸãã ãŸããdevone_ioctl.hãRustããèªããããã«ãbindingsã§devone_ioctl_bindings.rsãçæããŸãã
ãã®äžã§ããŠãŒã¶åŽãããlibc::ioctl()ãåŒã³åºããŸãã
use std::{fs::OpenOptions, io, os::fd::AsRawFd as _};
use tracing::info;
use uapp::bindings::devone_ioctl::{IOCTL_VALGET, IOCTL_VALSET, ioctl_cmd};
fn ioctl_valset(fd: i32, val: u32) -> io::Result<()> {
let mut data = ioctl_cmd {
reg: 0,
offset: 0,
val,
};
let rc = unsafe { libc::ioctl(fd, IOCTL_VALSET as libc::c_ulong, &mut data) };
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
fn ioctl_valget(fd: i32) -> io::Result<u32> {
let mut data = ioctl_cmd {
reg: 0,
offset: 0,
val: 0,
};
let rc = unsafe { libc::ioctl(fd, IOCTL_VALGET as libc::c_ulong, &mut data) };
if rc < 0 {
return Err(io::Error::last_os_error());
}
Ok(data.val)
}
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let f = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/devone0")?;
let fd = f.as_raw_fd();
let got = ioctl_valget(fd)?;
info!("IOCTL_VALGET: got val=0x{got:02x}");
let set_val = 2_u32;
info!("IOCTL_VALSET: setting val=0x{set_val:02x}");
ioctl_valset(fd, set_val)?;
let got = ioctl_valget(fd)?;
info!("IOCTL_VALGET: got val=0x{got:02x}");
Ok(())
}ãããå®è¡ãããš
2026-02-08T09:12:35.854053Z INFO devone_ioctl: IOCTL_VALGET: got val=0xff
2026-02-08T09:12:35.854128Z INFO devone_ioctl: IOCTL_VALSET: setting val=0x02
2026-02-08T09:12:35.854159Z INFO devone_ioctl: IOCTL_VALGET: got val=0x02ç¡äºioctlã§ããŸããã
次ã«ãpoll()ã§ããdeviceåŽã¯åžžã«read/writeå¯èœãªå®è£
ã§ã
static unsigned int devone_poll(struct file *filp, poll_table *wait)
{
struct devone_data *dev = filp->private_data;
if (dev == NULL)
return -EBADFD;
return POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
}ããã以äžã®ããã«ããŠåŒã³åºããŸããã
use std::{fs::OpenOptions, io, mem, os::fd::AsRawFd as _};
use anyhow::bail;
use tracing::info;
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let f = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/devone0")?;
let fd = f.as_raw_fd();
let epfd = unsafe { libc::epoll_create1(0) };
if epfd < 0 {
bail!(io::Error::last_os_error());
}
let mut ev: libc::epoll_event = unsafe { mem::zeroed() };
ev.events = libc::EPOLLIN as u32;
ev.u64 = fd as u64;
let rc = unsafe { libc::epoll_ctl(epfd, libc::EPOLL_CTL_ADD, fd, &mut ev as *mut _) };
if rc < 0 {
let err = io::Error::last_os_error();
unsafe { libc::close(epfd) };
bail!(err)
}
let mut events: [libc::epoll_event; 8] = unsafe { mem::zeroed() };
let n = unsafe { libc::epoll_wait(epfd, events.as_mut_ptr(), events.len() as i32, 0) };
if n < 0 {
let err = io::Error::last_os_error();
unsafe { libc::close(epfd) };
bail!(err)
}
info!("epoll_wait returned n={n}");
#[expect(clippy::needless_range_loop)]
for i in 0..n as usize {
let ev = &events[i];
let ready = ev.events;
let token = ev.u64;
info!("event[{i}]: token={token} events=0x{ready:x}");
if ready & (libc::EPOLLIN as u32) != 0 {
info!(" => EPOLLIN (readable)");
}
}
unsafe { libc::close(epfd) };
Ok(())
}epollã®è©³çްã¯å²æããŸããããããå®è¡ãããšç¡äºreadableã§ããããšãäŒããããŸãã
2026-02-08T09:24:22.709203Z INFO devone_epoll: epoll_wait returned n=1
2026-02-08T09:24:22.709280Z INFO devone_epoll: event[0]: token=123 events=0x1
2026-02-08T09:24:22.709301Z INFO devone_epoll: => EPOLLIN (readable)ãã®ä»ã«ããproc_fs, seq_file, sleepã®è§£èª¬ããããŸãã æåŸã«ãã©ã€ãããçŽæ¥ãinsmodãå®è¡ãããŠãŒã¶ã®ã¿ãŒããã«ã«ã¡ãã»ãŒãžãåºåããæ¹æ³ã玹ä»ãããŠããŸããã
static void pr_tty_console(char *msg)
{
struct tty_struct *tty;
tty = current -> signal -> tty;
if (tty != NULL) {
(tty->driver->ops->write)(tty, msg, strlen(msg));
(tty->driver->ops->write)(tty, "\r\n", 2);
}
}
static int __init devone_init(void) {
/* ... */
pr_tty_console("devone loaded(from tty console)\n");
/* ... */
}ããããéåžžã«å±éºãªåŠçã ãšæããŸãããå®è¡ããŠã¿ããš
sudo insmod ./devone.ko
devone loaded(from tty console)ãšãè¡šç€ºã«æåããŸããã ã¿ã€ããŒã«é¢ããAPIã¯çŸåšã§ã¯å€æŽãããŠãããå ·äœçãªå€æŽã«ã€ããŠã¯ãã«ãŒãã«ã¢ãžã¥ãŒã«äœæã§åŠã¶Linuxã«ãŒãã«éçºã®åºç€ç¥èïŒç¬¬3çãããããããã£ãã§ãã
第7ç« ããŒããŠã§ã¢å¶åŸ¡
æ¬ç« ã§ã¯ãã©ã€ãããå®éã«ããã€ã¹ãå¶åŸ¡ããããã«ãI/OããããI/Oãã¡ã¢ãªããããI/Oã«ã€ããŠã®è§£èª¬ããããŸãã ã¡ã¢ãªããããI/Oã®å Žåãããã€ã¹ã®ã¬ãžã¹ã¿ã«å¯Ÿå¿ããç©çã¢ãã¬ã¹ãæå®ããŠåœä»€ãå®è¡ããå¿
èŠããããŸããããã©ã€ãã¯ä»®æ³ã¢ãã¬ã¹ã§åããŠããã®ã§ãioremap()ãå¿
èŠãšãªããŸãã
ãŸããã¡ã¢ãªã¢ã¯ã»ã¹ã«å¯äœçšãäŸåé¢ä¿ãæé»çã«çããã®ã§ãã¡ã¢ãªããªã¢ãvolatileã®è©±ãã§ãŠããŸãã
asm()ã®è§£èª¬ããããRustã®asm!() macroã¯ããããæ¥ãã®ããšç¥ããŸããã
第8ç« ã¡ã¢ãª
ã«ãŒãã«ã®ã¡ã¢ãªç®¡çã«ã€ããŠãå
·äœçã«ã¯ãkmalloc(),kfree()ãšãã£ãAPIãã¹ã©ãã¢ãã±ãŒã¿ãããã£ã·ã¹ãã ãã«ãŒãã«ã¹ã¿ãã¯ã«ã€ããŠã®èšåããããŸãã
DMAã«ã€ããŠã¯ãå®éã®ãã©ã€ã(drivers/net/e100.c)ã§ã®å©çšäŸãããã£ãã·ã¥ã³ããŒã¬ã³ã¹åé¡ã®è§£èª¬ããããŸãã
第9ç« ã¿ã€ã
2.6æç¹ã§ã®æé管çã«ã€ããŠã jiffiesãdo_gettimeofday(), timevalãã§ãŠããŸãã 2038幎åé¡ã497æ¥åé¡ã«ããµããããŠããŸãã ãã©ã€ãã§ã¯ãããã€ã¹ã«å¯ŸããŠDMAã®éå§ãæç€ºããéåžžã§ã¯å®äºæã®å²èŸŒã¿ã§åŠçãããŸãããããããã¯æåããã±ãŒã¹ã§ãããã€ã¹åŽã®äžå
·åã§å²èŸŒã¿ãããã£ãŠããªããããããªãã®ã§ãå¿
ãã¿ã€ã ã¢ãŠãåŠçããããå¿
èŠããããŸãã
ä»åã¯ä»¥äžã®ããã«ããŠãreadæã®ã¬ã€ãã³ã·ãŒãã¢ãã¯ããããã«ã¿ã€ã ã¢ãŠãåŠçããããŠã¿ãŸããã
struct devone_data {
/* ... */
/* mock read latency */
wait_queue_head_t read_wq;
atomic_t read_ready;
struct timer_list read_timer;
};static int devone_open(struct inode *inode, struct file *file) {
struct devone_data *p;
p = kmalloc(sizeof(struct devone_data), GFP_KERNEL);
if (p == NULL) {
printk("%s: No memory\n", __func__);
return -ENOMEM;
}
p->val = 0xff;
rwlock_init(&p->lock);
/* prepare mock read latency */
init_waitqueue_head(&p->read_wq);
atomic_set(&p->read_ready, 1);
timer_setup(&p->read_timer, devone_read_timer_cb, 0);
file->private_data = p;
return 0;
}openæã«ãtimer_setup()ã§timer_listã«callback颿°ãdevone_read_timer_cbãç»é²ããŸãã
static void devone_read_timer_cb(struct timer_list *t)
{
struct devone_data *p = container_of(t, struct devone_data, read_timer);
atomic_set(&p->read_ready, 1);
pr_info("%s: Wake up!\n", __func__);
wake_up(&p->read_wq);
}devone_read_timer_cbã¯flag倿°ã§å®äºãèšé²ããŠãwake_up()ãåŒã³ãŸãã
#define DEVONE_READ_DELAY_MS 1000
static ssize_t devone_read(struct file *filep, char __user *buf, size_t count,
loff_t *f_pos) {
struct devone_data *p = filep->private_data;
int i;
unsigned char val;
int retval;
long wr;
atomic_set(&p->read_ready,0);
mod_timer(&p->read_timer, jiffies + msecs_to_jiffies(DEVONE_READ_DELAY_MS));
wr = wait_event_interruptible_timeout(
p->read_wq,
atomic_read(&p->read_ready) != 0,
msecs_to_jiffies(DEVONE_READ_DELAY_MS + 1000)
);
if (wr == 0)
return -ETIMEDOUT;
if (wr < 0)
return wr;
/* readåŠç */
}readæã«ãmod_timer()ã§timeoutåŠçãæå¹åããŠãwait_event_interruptible_timeout()ã§ã¬ã€ãã³ã·ãã¢ãã¯ããŸãã
#define DEVONE_READ_DELAY_MS 5000 ã§5ç§ãæå®ããŠã¿ããšç¡äºãã¢ãã¯ã§ããŸãã
time hexyl -n 1 /dev/devone0 out> /dev/null
0.00user 0.00system 0:05.13elapsed 0%CPU (0avgtext+0avgdata 2676maxresident)k
0inputs+0outputs (0major+130minor)pagefaults 0swapsãŸããtimerãçºç«ããåã«closeã§ãªãœãŒã¹ãéæŸãããå Žåãããã®ã§ãtimer_delete_sync()ãåŒã³åºããŠããå¿
èŠããããŸãã
第10ç« åæãšæä»
å ±æãªãœãŒã¹ã«ã¢ã¯ã»ã¹ããå Žåã«ãããã¯ããšã£ãããã¢ãããã¯æäœãè¡ãã®ã¯ãäžè¬çãªã¢ããªã±ãŒã·ã§ã³ãšåãã§ãã ãã©ã€ãã§ç°ãªãã®ã¯ãå²èŸŒã¿ã³ã³ããã¹ãã§ã¯sleepã§ããªãã®ã§ãspin_lockç³»ã®APIãçšããå¿ èŠãããã®ãšãå²èŸŒã¿ã®æå¹/ç¡å¹ãæèããå¿ èŠããããšããããšãããããŸããã kthreadã®èª¬æããããŸãããåããã ããªã以äžã®ããã«ãªããŸããã
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/kthread.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ymgyt");
MODULE_DESCRIPTION("kthread sample");
#define DRIVER_NAME "kthread"
static struct task_struct *kmain_task = NULL;
static int sample_thread(void *num)
{
while (!kthread_should_stop()) {
msleep_interruptible(3000);
}
return 0;
}
static int __init mykthread_init(void) {
kmain_task = kthread_create(sample_thread, NULL, "sample mykthread");
if (IS_ERR(kmain_task)) {
return PTR_ERR(kmain_task);
}
wake_up_process(kmain_task);
return 0;
}
static void __exit mykthread_exit(void) {
if (kmain_task) {
kthread_stop(kmain_task);
}
}
module_init(mykthread_init);
module_exit(mykthread_exit);sudo insmod ./"kthread".ko
ps | where name =~ "sample mykthread"
# | pid | ppid | name | status | cpu | mem | virtual
---+---------+------+------------------+----------+------+-----+---------
0 | 1216364 | 2 | sample mykthread | Sleeping | 0.00 | 0 B | 0 B第11ç« å²èŸŒã¿
ãã©ã€ããšããã°å²èŸŒã¿ãšããããšã§å²èŸŒã¿ã®æŠå¿µã«ã€ããŠèª¬æãããŸãã ãŸãå®éã®å²èŸŒã¿åŠçãšããŠãdrivers/net/8139too.cãåãäžããããŠããŸããã 以äžã¯ãdrivers/net/ethernet/realtek/8139too.cã®rtl8139_interrupt()åŠçã§ããæ¬æžã®ãããŸã§ã®ç¥èã§é°å²æ°ã§ããèªããããã«ãªã£ãŠããŸãã
/* The interrupt handler does all of the Rx thread work and cleans up
after the Tx thread. */
static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance)
{
struct net_device *dev = (struct net_device *) dev_instance;
struct rtl8139_private *tp = netdev_priv(dev);
void __iomem *ioaddr = tp->mmio_addr;
u16 status, ackstat;
int link_changed = 0; /* avoid bogus "uninit" warning */
int handled = 0;åŒæ°ããåŠççšã®privateãªstructãåãåºããã¡ã¢ãªããããI/Oçšã¢ãã¬ã¹ãä¿æããŸãã
spin_lock (&tp->lock);
status = RTL_R16 (IntrStatus);ã¬ãžã¹ã¿ã«ã¢ã¯ã»ã¹ããã«ããã£ãŠãlockãååŸããŸããå²èŸŒã¿åŠçå ãªã®ã§ãirqsaveçã¯äžèŠãšæãããŸãã
#define RTL_R16(reg) ioread16 (ioaddr + (reg))
/* Symbolic offsets to registers. */
enum RTL8139_registers {
/* ... */
IntrStatus = 0x3E,RTL_R16ã¯ã¬ãžã¹ã¿ãreadãããã¯ãã§ãstatusã«ããããå²èŸŒã¿é¢é£ã®æ
å ±ãå
¥ã£ãŠãããšæãããŸãã ioaddrã¯äºåã«stackã«ç¢ºä¿ããŠããåæã®ããã§ãã
/* shared irq? */
if (unlikely((status & rtl8139_intr_mask) == 0))
goto out;
handled = 1;irq(å²èŸŒã¿ãã³ãã©)ã¯ãå ±æããããã®ã§ãåŠçãäžèŠãªãreturn
/* h/w no longer present (hotplug?) or major error, bail */
if (unlikely(status == 0xFFFF))
goto out;
/* close possible race's with dev_close */
if (unlikely(!netif_running(dev))) {
RTL_W16 (IntrMask, 0);
goto out;
}åŠçãç¶ç¶ã§ãããã®ç¢ºèªãšæãããŸãã
ackstat = status & ~(RxAckBits | TxErr);
if (ackstat)
RTL_W16 (IntrStatus, ackstat);å²èŸŒã¿èŠå ã®ã¯ãªã¢ãRxAckBitsãšTxErrã¯ãã®ããšã®åŠçã§åç
§ããã®ã§ãã以å€ã®ACKåŠçã§ããããã
/* Receive packets are processed by poll routine.
If not running start it now. */
if (status & RxAckBits){
if (napi_schedule_prep(&tp->napi)) {
RTL_W16_F (IntrMask, rtl8139_norx_intr_mask);
__napi_schedule(&tp->napi);
}
}åä¿¡åŠçã¯ãNAPI ãšããä»çµã¿ãå©çšããŠããããããåŠçãç§»è²
/* Check uncommon events with one test. */
if (unlikely(status & (PCIErr | PCSTimeout | RxUnderrun | RxErr)))
rtl8139_weird_interrupt (dev, tp, ioaddr,
status, link_changed);Uncommonãªå²èŸŒã¿ãåŠç
if (status & (TxOK | TxErr)) {
rtl8139_tx_interrupt (dev, tp, ioaddr);
if (status & TxErr)
RTL_W16 (IntrStatus, TxErr);
}
out:
spin_unlock (&tp->lock);
netdev_dbg(dev, "exiting interrupt, intr_status=%#4.4x\n",
RTL_R16(IntrStatus));
return IRQ_RETVAL(handled);
}éä¿¡ãå®äºããŠããå Žåã®åŸåŠçãšlockéæŸã ãšããããšã§é°å²æ°ã§ããã©ããªããšãããŠããã®ãããããŸããã
ãŸããå²èŸŒã¿åŠçã§ã¯æäœéã®åŠçãè¡ããæ®ãã®åŠçãå¥ã®æ©æ§ã«ãŸãããä»çµã¿ãšããŠãtaskletãšworkqueueã®ç޹ä»ããããŸããã å²èŸŒã¿åŠçã«ã€ããŠã¯ãæ°Linuxã«ãŒãã«è§£èªå®€ - ãœããå²ã蟌ã¿åŠçããæ°Linuxã«ãŒãã«è§£èªå®€ - Workqueueã®è§£èª¬ãéåžžã«ãããããã£ãã§ãã
第12ç« PCI
PCIããã€ã¹ãšãã©ã€ãã®å¯Ÿå¿é¢ä¿ã®èª¿ã¹æ¹ããå§ãŸããŸãã 詊ãã«ãWi-Fiã®ãã©ã€ãã調ã¹ãŠã¿ããš
# -k Show kernel drivers handling each device and also kernel modules capable of handling it.
# -nn Show PCI vendor and device codes as both numbers and names.
lspci -knn | rg -i 'wireless network' -A 3
pcilib: Error reading /sys/bus/pci/devices/0000:00:08.3/label: Operation not permitted
01:00.0 Network controller [0280]: MEDIATEK Corp. MT7922 802.11ax PCI Express Wireless Network Adapter [14c3:0616] (rev 02)
Subsystem: MEDIATEK Corp. Device [14c3:223c]
Kernel driver in use: mt7921e
Kernel modules: mt7921eWireless Network Adapter [14c3:0616] ããVendorIDã
14c3ã§device codeã0616ãšããããŸããã ãŸããmoduleã¯mt7921eãå©çšãããŠããã®ã§ kernelã®drivers/net/wireless/mediatek/mt76/mt7921/Makefileãã¿ãŠã¿ããš
# ...
obj-$(CONFIG_MT7921E) += mt7921e.o
# ...
mt7921e-y := pci.o pci_mac.o pci_mcu.oãšmt7921e ã¢ãžã¥ãŒã«ãå®çŸ©ãããŠããããã©ã€ãåŽã§å¯Ÿå¿ããããã€ã¹ã宣èšããpci_device_id[]ãã¿ããš
rg 'static const struct pci_device_id mt7921_pci_device_table' -A 15 drivers/net/wireless/mediatek/mt76/mt7921/pci.c
16:static const struct pci_device_id mt7921_pci_device_table[] = {
17- { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7961),
18- .driver_data = (kernel_ulong_t)MT7921_FIRMWARE_WM },
19- { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7922),
20- .driver_data = (kernel_ulong_t)MT7922_FIRMWARE_WM },
21- { PCI_DEVICE(PCI_VENDOR_ID_ITTIM, 0x7922),
22- .driver_data = (kernel_ulong_t)MT7922_FIRMWARE_WM },
23- { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0608),
24- .driver_data = (kernel_ulong_t)MT7921_FIRMWARE_WM },
25- { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0616),
26- .driver_data = (kernel_ulong_t)MT7922_FIRMWARE_WM },
27- { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7920),
28- .driver_data = (kernel_ulong_t)MT7920_FIRMWARE_WM },
29- { },
30-};
31-ãšå®çŸ©ãããŠãããinclude/linux/pci_ids.hã«
#define PCI_VENDOR_ID_MEDIATEK 0x14c3ãå®çŸ©ãããŠããŸãããŸããpci_device_idã¯include/linux/mod_devicetable.hã«ä»¥äžã®ããã«å®çŸ©ãããŠããŸãã
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
__u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data; /* Data private to the driver */
__u32 override_only;
};ãããã
25- { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0616),
26- .driver_data = (kernel_ulong_t)MT7922_FIRMWARE_WM },ã®ãšã³ããªãŒãlspciã®Wireless Network Adapter [14c3:0616]ã«å¯Ÿå¿ããŠããããªããšãããããŸããã (æ¬æžã®ãããã§ãã©ã€ãã®ã³ãŒãã¡ãã£ãšèŠãŠã¿ãããšããæ°æã¡ã«ãªããã®ãããããã§ãã)
ãã®ããšãMMIO, I/OããŒããPCIã³ã³ãã£ã°ã¬ãŒã·ã§ã³ç©ºéã®è©³ãã解説ãç¶ããŸãã
PCIãã©ã€ãã®å
·äœäŸãšããŠã8139too.cã®è§£èª¬ããããŸãã PCIãã©ã€ãã®åºæ¬çãªåŠçãšããŠãããã€ã¹ã®ã¬ãžã¹ã¿ã«ã¢ã¯ã»ã¹ããããã«ãPCIã³ã³ãã£ã°ã¬ãŒã·ã§ã³ç©ºéããBARæ
å ±ãååŸããŠãioremap()(BARã¯ç©çã¢ãã¬ã¹ãªã®ã§ãã®ãŸãŸã§ã¯ä»®æ³ã¢ãã¬ã¹ãšããŠã¢ã¯ã»ã¹ã§ããªããã)ãããšãããŸã§ã®èª¬æã§ããããŸãããããã§ã8139too.cã®å®è£
ãã¿ãŠã¿ããš
drivers/net/ethernet/realtek/8139too.c
static struct net_device *rtl8139_init_board(struct pci_dev *pdev)
{
struct device *d = &pdev->dev;
void __iomem *ioaddr;
struct net_device *dev;
struct rtl8139_private *tp;
unsigned int i, bar;
/* omitted... */
ioaddr = pci_iomap(pdev, bar, 0);
if (!ioaddr) {
/* omitted... */
rc = -ENODEV;
goto err_out;
}
tp->regs_len = io_len;
tp->mmio_addr = ioaddr;ãšpci_iomap()ãå©çšããŠãããdrivers/pci/iomap.cãã¿ãŠã¿ããš
void __iomem *pci_iomap(struct pci_dev *dev, int bar, unsigned long maxlen)
{
return pci_iomap_range(dev, bar, 0, maxlen);
}
EXPORT_SYMBOL(pci_iomap);
void __iomem *pci_iomap_range(struct pci_dev *dev,
int bar,
unsigned long offset,
unsigned long maxlen)
{
resource_size_t start, len;
unsigned long flags;
if (!pci_bar_index_is_valid(bar))
return NULL;
start = pci_resource_start(dev, bar);
len = pci_resource_len(dev, bar);
flags = pci_resource_flags(dev, bar);
if (len <= offset || !start)
return NULL;
len -= offset;
start += offset;
if (maxlen && len > maxlen)
len = maxlen;
if (flags & IORESOURCE_IO)
return __pci_ioport_map(dev, start, len);
if (flags & IORESOURCE_MEM)
return ioremap(start, len);
/* What? */
return NULL;
}
EXPORT_SYMBOL(pci_iomap_range);ãšpci_resource_{start,len}ã§BARãååŸããMMIO,I/OããŒããå€å®ããŠãioremap()ãå®è¡ããŠããŸããã
ãããŸã§ã¯ãPCIã®è©±ã§ããããPCI Expressã説æãããŠããã©ããã£ãæ¡åŒµããã£ãã®ãã®è§£èª¬ããããŸãã
第13ç« ã·ãªã¢ã«ãã¹
I2C(SMBus)ã«ã€ããŠã ãããã³ã«ã®æŠèŠãããã¹å¶åŸ¡æ¹æ³ã®èª¬æããããŸããèªåã¯çµã¿èŸŒã¿ã«éŠŽæã¿ããªãã®ã§ããã®ãããã®è§£èª¬ã¯éåžžã«åèã«ãªããŸããã ãŸããlm_sensorsããã±ãŒãžã®èª¬æããããŸãã å®è£
ã«ã€ããŠã¯ãkernelã®I2Cãi2c-dev, i2c-core, i2c-algo, i2c adapterãšãã£ãã¢ãžã¥ãŒã«ã¹ã¿ãã¯ãããªããšãã£ã解説ããããŸãã
第14ç« ACPI
ACPI(Advanced Configuration and Power Interface)ã«ã€ããŠã ACPIã¯é»æºç®¡çã ãã§ãªããèµ·åæã« kernelãPCIeãå©çšå¯èœã«ããåæå(ãã¹ãåŽã®èªèãèšå®ç©ºéã¢ã¯ã»ã¹ã®æºå)ã«ãé¢äžãããããã 黿ºãã¿ã³æŒäžã ACPI çµç±ã§éç¥ããããŠãŒã¶ãŒç©ºéã®ããªã·ãŒã«åŸã£ãŠã·ã£ããããŠã³çãå®è¡ãããã
第15ç« IPMI
IPMI(Intelligent Platform Management Interface)ã«ã€ããŠã ãã¡ãã¯æ®æ®µé¢ããããªãã®ã§å²æããŸããã(74pçšåºŠãšãã£ãã解説ããããŸã)
第16ç« ãã¹ããšãããã°
Kernelãã«ãæã®ãã©ã°ãprintkãMagic SysRqããŒãOopsã¡ãã»ãŒãžã«ã€ããŠã®è§£èª¬ããããŸãã
第17ç« ãã©ã€ãèšèšãšå®è£ ã®å®é
drivers/net/8139too.c(ä»ã ãšdrivers/net/ethernet/realtek/8139too.c)ãäŸã«ãããã¯ãŒã¯ãã©ã€ãã®å®è£
ã®è§£èª¬ããããŸãã
ãŸãšã
Rustã®sample driverã®ã³ãŒãèªãããã«èªãã§ã¿ãŸãããããã©ã€ãã«éããåèã«ãªãç¹ãå€ãéåžžã«ãããããã£ãã§ããKernelã«é¢ããæ¬ã¯æè¿Linuxã«ãŒãã«ããã°ã©ãã³ã°ç¬¬2çãåºçãããŸããããäžææéçµ¶ããŠããå°è±¡ãããã2000幎代ã«å€ãåºçãããŠããå°è±¡ããããŸãã(è±èªã ãšãããããããŸãã)
æ¬æžãèªãã«ããããLinux Device Driver Development: Everything you need to start with device driver development for Linux kernel and embedded Linux, 2nd Edition, ã«ãŒãã«ã¢ãžã¥ãŒã«äœæã§åŠã¶Linuxã«ãŒãã«éçºã®åºç€ç¥èïŒç¬¬3ç,Linuxã«ãŒãã«ã¢ãžã¥ãŒã«èªäœå ¥éçãåèã«ãããŠããã ããŸããã