/dev/memと/dev/kmem
/dev/memは、物理メモリを読み書きするデバイスファイルです。ユーザプロセス下でIOマップデバイスの実装するためのようです。このファイルオペレーションはmem_fopsとなります。
whileループでcount値に従って、物理メモリをページサイズごとに、copy_to_user()でユーザバッファのbufに転送します。なお、copy_to_user()の引数は仮想アドレスです。そこでxlate_dev_mem_ptr()で読み込む物理アドレスを仮想アドレスに変換したアドレスで転送しています。
page_is_ram()がNGなら、ioremap_cache()で、その物理アドレスに仮想アドレスを割り当てることで参照できるようにしています。(これはBIOS領域、予約メモリ等のケース)
カーネルコンパイルオプションCONFIG_DEVKMEMで、/dev/kmemデバイスファイルが作成されます。機能としては/dev/memと同じように、物理メモリを参照するものですが、より機能が拡張されているようです。/dev/kmemのfile_operationsは、kmem_fopsです。
物理メモリがhigh_memory以降の場合、なんか新たにページを取得し、そこに物理アドレスを割り当ててることで、そのページをユーザバッファに転送することで実現しているみたいですが・・・。よく分かりません。
なお、物理メモリがhigh_memory以下の場合、処理は/dev/memと同じようです。ただし物理アドレスを仮想アドレスに変換する関数が、xlate_dev_kmem_ptr()と異なっています。
static const struct file_operations mem_fops = {
.llseek = memory_lseek,
.read = read_mem,
.write = write_mem,
.mmap = mmap_mem,
.open = open_mem,
.get_unmapped_area = get_unmapped_area_mem,
};
読み込みのコールバックはread_mem()で、まずvalid_phys_addr_range()で、読み込み終了アドレスが、high_memoryを越えていないかチェックします。越えていたらエラーです。すなわち/dev/memは、カーネル空間のみのアドレスしか参照できません。whileループでcount値に従って、物理メモリをページサイズごとに、copy_to_user()でユーザバッファのbufに転送します。なお、copy_to_user()の引数は仮想アドレスです。そこでxlate_dev_mem_ptr()で読み込む物理アドレスを仮想アドレスに変換したアドレスで転送しています。
static ssize_t read_mem(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
ssize_t read, sz;
char *ptr;
if (!valid_phys_addr_range(p, count))
return -EFAULT;
read = 0;
while (count > 0) {
unsigned long remaining;
sz = size_inside_page(p, count);
if (!range_is_allowed(p >> PAGE_SHIFT, count))
return -EPERM;
ptr = xlate_dev_mem_ptr(p);
if (!ptr)
return -EFAULT;
remaining = copy_to_user(buf, ptr, sz);
unxlate_dev_mem_ptr(p, ptr);
if (remaining)
return -EFAULT;
buf += sz;
p += sz;
count -= sz;
read += sz;
}
*ppos += read;
return read;
}
static inline int valid_phys_addr_range(unsigned long addr, size_t count)
{
return addr + count <= __pa(high_memory);
}
xlate_dev_mem_ptr()で、物理アドレスを仮想アドレスに変換します。page_is_ram()は、物理アドレスの正当性をチェックします。(カーネルがストレートマップしている領域かどうかのチェック?)OKなら__vaマクロで仮想アドレスに変換します。カーネル空間はストレートマップしているため、これは物理アドレスにPAGE_OFFSETを加算するだけです。page_is_ram()がNGなら、ioremap_cache()で、その物理アドレスに仮想アドレスを割り当てることで参照できるようにしています。(これはBIOS領域、予約メモリ等のケース)
void *xlate_dev_mem_ptr(unsigned long phys)
{
void *addr;
unsigned long start = phys & PAGE_MASK;
if (page_is_ram(start >> PAGE_SHIFT))
return __va(phys);
addr = (void __force *)ioremap_cache(start, PAGE_SIZE);
if (addr)
addr = (void *)((unsigned long)addr | (phys & ~PAGE_MASK));
return addr;
}
page_is_ram()で物理アドレスの正当性をチェックします。BIOS領域ならNGです。そしてBIOSで取得している物理メモリ(e820)から、物理アドレスの正当性をチェックしています。
int page_is_ram(unsigned long pagenr)
{
resource_size_t addr, end;
int i;
if (pagenr >= (BIOS_BEGIN >> PAGE_SHIFT) &&
pagenr < (BIOS_END >> PAGE_SHIFT))
return 0;
for (i = 0; i < e820.nr_map; i++) {
if (e820.map[i].type != E820_RAM)
continue;
addr = (e820.map[i].addr + PAGE_SIZE-1) >> PAGE_SHIFT;
end = (e820.map[i].addr + e820.map[i].size) >> PAGE_SHIFT;
if ((pagenr >= addr) && (pagenr < end))
return 1;
}
return 0;
}
__va(x)マクロで物理アドレスを仮想アドレス(リニアアドレス)に変換します。ここでこのマクロを使っているのは、物理アドレスが、カーネル空間だからです。#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
カーネルコンパイルオプションCONFIG_DEVKMEMで、/dev/kmemデバイスファイルが作成されます。機能としては/dev/memと同じように、物理メモリを参照するものですが、より機能が拡張されているようです。/dev/kmemのfile_operationsは、kmem_fopsです。
#ifdef CONFIG_DEVKMEM
static const struct file_operations kmem_fops = {
.llseek = memory_lseek,
.read = read_kmem,
.write = write_kmem,
.mmap = mmap_kmem,
.open = open_kmem,
.get_unmapped_area = get_unmapped_area_mem,
};
#endif
read_kmem()は読み込みのコールバック関数です。/dev/kmemは/dev/memと異なり、valid_phys_addr_range()による物理メモリレンジのチェックを行っていません。すなわち/dev/kmemは全物理メモリを参照することができると言うことです。物理メモリがhigh_memory以降の場合、なんか新たにページを取得し、そこに物理アドレスを割り当ててることで、そのページをユーザバッファに転送することで実現しているみたいですが・・・。よく分かりません。
なお、物理メモリがhigh_memory以下の場合、処理は/dev/memと同じようです。ただし物理アドレスを仮想アドレスに変換する関数が、xlate_dev_kmem_ptr()と異なっています。
static ssize_t read_kmem(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
ssize_t low_count, read, sz;
char * kbuf;
int err = 0;
read = 0;
if (p < (unsigned long) high_memory) {
low_count = count;
if (count > (unsigned long)high_memory - p)
low_count = (unsigned long)high_memory - p;
while (low_count > 0) {
sz = size_inside_page(p, low_count);
kbuf = xlate_dev_kmem_ptr((char *)p);
if (copy_to_user(buf, kbuf, sz))
return -EFAULT;
buf += sz;
p += sz;
read += sz;
low_count -= sz;
count -= sz;
}
}
if (count > 0) {
kbuf = (char *)__get_free_page(GFP_KERNEL);
if (!kbuf)
return -ENOMEM;
while (count > 0) {
sz = size_inside_page(p, count);
if (!is_vmalloc_or_module_addr((void *)p)) {
err = -ENXIO;
break;
}
sz = vread(kbuf, (char *)p, sz);
if (!sz)
break;
if (copy_to_user(buf, kbuf, sz)) {
err = -EFAULT;
break;
}
count -= sz;
buf += sz;
read += sz;
p += sz;
}
free_page((unsigned long)kbuf);
}
*ppos = p;
return read ? read : err;
}
xlate_dev_kmem_ptrマクロは、物理アドレスそのものを返します。従って/dev/kmemで指定するアドレスは、カーネル空間のリニアアドレスである必要があります。#define xlate_dev_kmem_ptr(p) p
補足
/dev/mem,/dev/kmemは、ユーザプロセス下で、IOマップデバイスを操作するために、実装されたものだと推測できます。従ってユーザプロセスからカーネルログに出力するために、/dev/kmsgが実装されています。これはログメッセージとして指定されたユーザバッファを、printk()で出力するものです。
static const struct file_operations kmsg_fops = {
.aio_write = kmsg_writev,
.llseek = noop_llseek,
};
static ssize_t kmsg_writev(struct kiocb *iocb, const struct iovec *iv,
unsigned long count, loff_t pos)
{
char *line, *p;
int i;
ssize_t ret = -EFAULT;
size_t len = iov_length(iv, count);
line = kmalloc(len + 1, GFP_KERNEL);
if (line == NULL)
return -ENOMEM;
p = line;
for (i = 0; i < count; i++) {
if (copy_from_user(p, iv[i].iov_base, iv[i].iov_len))
goto out;
p += iv[i].iov_len;
}
p[0] = '\0';
ret = printk("%s", line);
/* printk can add a prefix */
if (ret > len)
ret = len;
out:
kfree(line);
return ret;
}






