LinuxのARMv7でのMMU faultを追いかける

2014/12/21 linux::ARM

Linux(ARMv7系)のメモリ管理を追ってみる

Linux Advent Calendar 2014の21日目の記事です。

kernel sourceの追いかけかた

前回のcopy_to_user()のfault処理実装を見たときに、__do_kernel_fault()あたりから先はちらっと見たので、そもそもメモリ管理全般はどうなっているのだろう、と気になって仕方が無くなる。
とりあえず下から覗いていくのが低レイヤー民(そんなものはない)のたしなみだろう、ということで、最近メジャーなARMv7アーキテクチャをターゲットとして調査する。

kernelのメモリ管理の基本と、ARMアーキテクチャでの実装を確認し、faultハンドラを追いかけていこう。

追いかけ方は人それぞれかもしれないけれど、Makefileにtagsがターゲットとして存在しているので、マクロやプリプロセッサが多いので、適当なターゲットでkernel sourceをbuildしておき、tagsも作っておくと楽ができる。マクロで関数を生成しているところもあるので、すべてタグジャンプできるとは限らないので注意も必要だ...
迷ったらバイナリを逆アセンブルするとかすればヒントにもなろう。
それでもだめならgrepするなり舐めるなりするといい(?)。

メモリ管理の基本

gorman氏のUnderstanding the Linux Virtual Memory Managerを見ればだいたい判るような気がしてきた。こんなトコロ読むよりも有意だと思う(´・ω・)

ココを参照されている諸兄は、すでにカーネル空間とユーザ空間とかの存在は把握されていると思います。
で、Virtual AddressをPhysical Addressに変換する機能はARMv7Aの場合はMMUが物理メモリを参照して勝手に変換しちゃう。
ということまで理解しているとしましょう。

Linuxの場合、基本的に4kByte/pageを使うことにしてあるので、第二レベル変換テーブルはsmall sectionが選択されています。
必然的に、第一レベル変換はSection Baseの変換テーブルを用いることになります。
Section baseの場合は、1MiBごとに第二レベル変換テーブルの先頭アドレスが置かれます。

順序が逆になりますが、Linuxのメモリ管理テーブルについて文字だけでまとめます。
論理アドレスを上位ビットからテーブル引きをして、PGD(Page Global Directory ),
PUD(Page Upper Directory), PMD(Page Middle Directory), PTE(Page Table Entries )の順に
4段階でページテーブルをインデックスします。
先に示した資料ではPUDが無いので、どこかで追加されたのでしょう。

linux/mm/配下は、この概念をベースにして実装されており、アーキテクチャに依存する
アドレス空間の差異を吸収できる仕組みになっています。
Linuxの論理-物理変換管理テーブルが定義されており、汎用性を持った記述になっています。
アーキテクチャ依存部でうまくコードを共通化しているのでそのあたりも見ていきましょう。
1. VAを元に PGDから PUDの先頭アドレスを得る。
2. PUDとVAから PMDの先頭アドレスを得る(arm32ではPUDは未使用,PGD=>PMDとなっている)
3. PMDとVAから PTEの先頭アドレスを得る(PGDがPMDをインデックスしている、と見る)。
4. PTEとVAから 該当するページの物理アドレスや属性や状態を得る。

arm32/LPAEなしでは、pgdレベル1変換テーブル、PMDがレベル2変換テーブル、
pteがレベル2変換テーブルの該当アドレスにあたるSmall page要素になります。
PUD,PMDの取得は、pud_offset()とpmd_offset()で実装されますが、pgd,pudをそのまま返しています。
関連するシンボルは以下のとおりで、ビット幅をセットします。
symbol value
PAGE_SHIFT12(4k)
PMD_SHIFT21
SECTION_SHIFT20
SUPERSECTION_SHIFT20
下位12bitはページ単位になるので、変換テーブルによって指される最小単位となります。
Cortex-Aシリーズになると、不要な気がしますが、過去の経緯でPGDは2GiB空間をインデックスするように設計されています。
accessed/dirty bitがpteに存在しないので それをエミュレートするため, とありますが,
ここは消化しきれていません. arm依存部でも ARMv4,v5,v7,v8と差異はそこそこありそうです。


ARMv7Aの場合

ここで、LAPEなしと仮定して、以下を参照する(LAPEありなら03level)。

FILE:arch/arm/include/asm/pgtable-2level.h
/*
 * Hardware-wise, we have a two level page table structure, where the first
 * level has 4096 entries, and the second level has 256 entries.  Each entry
 * is one 32-bit word.  Most of the bits in the second level entry are used
 * by hardware, and there aren't any "accessed" and "dirty" bits.
 *
 * Linux on the other hand has a three level page table structure, which can
 * be wrapped to fit a two level page table structure easily - using the PGD
 * and PTE only.  However, Linux also expects one "PTE" table per page, and
 * at least a "dirty" bit.
 *
 * Therefore, we tweak the implementation slightly - we tell Linux that we
 * have 2048 entries in the first level, each of which is 8 bytes (iow, two
 * hardware pointers to the second level.)  The second level contains two
 * hardware PTE tables arranged contiguously, preceded by Linux versions
 * which contain the state information Linux needs.  We, therefore, end up
 * with 512 entries in the "PTE" level.
 *
 * This leads to the page tables having the following layout:
 *
 *    pgd             pte
 * |        |
 * +--------+
 * |        |       +------------+ +0
 * +- - - - +       | Linux pt 0 |
 * |        |       +------------+ +1024
 * +--------+ +0    | Linux pt 1 |
 * |        |-----> +------------+ +2048
 * +- - - - + +4    |  h/w pt 0  |
 * |        |-----> +------------+ +3072
 * +--------+ +8    |  h/w pt 1  |
 * |        |       +------------+ +4096
 *
 * See L_PTE_xxx below for definitions of bits in the "Linux pt", and
 * PTE_xxx for definitions of bits appearing in the "h/w pt".
 *
 * PMD_xxx definitions refer to bits in the first level page table.
 *
 * The "dirty" bit is emulated by only granting hardware write permission
 * iff the page is marked "writable" and "dirty" in the Linux PTE.  This
 * means that a write to a clean page will cause a permission fault, and
 * the Linux MM layer will mark the page dirty via handle_pte_fault().
 * For the hardware to notice the permission change, the TLB entry must
 * be flushed, and ptep_set_access_flags() does that for us.
 *
 * The "accessed" or "young" bit is emulated by a similar method; we only
 * allow accesses to the page if the "young" bit is set.  Accesses to the
 * page will cause a fault, and handle_pte_fault() will set the young bit
 * for us as long as the page is marked present in the corresponding Linux
 * PTE entry.  Again, ptep_set_access_flags() will ensure that the TLB is
 * up to date.
 *
 * However, when the "young" bit is cleared, we deny access to the page
 * by clearing the hardware PTE.  Currently Linux does not flush the TLB
 * for us in this case, which means the TLB will retain the transation
 * until either the TLB entry is evicted under pressure, or a context
 * switch which changes the user space mapping occurs.
 */
#define PTRS_PER_PTE            512
#define PTRS_PER_PMD            1
#define PTRS_PER_PGD            2048

#define PTE_HWTABLE_PTRS        (PTRS_PER_PTE)
#define PTE_HWTABLE_OFF         (PTE_HWTABLE_PTRS * sizeof(pte_t))
#define PTE_HWTABLE_SIZE        (PTRS_PER_PTE * sizeof(u32))

/*
 * PMD_SHIFT determines the size of the area a second-level page table can map
 * PGDIR_SHIFT determines what a third-level page table entry can map
 */
#define PMD_SHIFT               21
#define PGDIR_SHIFT             21

#define PMD_SIZE                (1UL << PMD_SHIFT)
#define PMD_MASK                (~(PMD_SIZE-1))
#define PGDIR_SIZE              (1UL << PGDIR_SHIFT)
#define PGDIR_MASK              (~(PGDIR_SIZE-1))

FILE: arch/arm/include/asm/pgtable.h
/* to find an entry in a page-table-directory */
#define pgd_index(addr)		((addr) >> PGDIR_SHIFT)  /// 21bit@LAPEなし

#define pgd_offset(mm, addr)	((mm)->pgd + pgd_index(addr))

/* to find an entry in a kernel page-table-directory */
#define pgd_offset_k(addr)	pgd_offset(&init_mm, addr)


static inline pte_t *pmd_page_vaddr(pmd_t pmd)
{
	return __va(pmd_val(pmd) & PHYS_MASK & (s32)PAGE_MASK);
}

#ifndef CONFIG_HIGHPTE
#define __pte_map(pmd)		pmd_page_vaddr(*(pmd))
#define __pte_unmap(pte)	do { } while (0)
#else
#endif
#define pte_index(addr)		(((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
#define pte_offset_kernel(pmd,addr)	(pmd_page_vaddr(*(pmd)) + pte_index(addr))
#define pte_offset_map(pmd,addr)	(__pte_map(pmd) + pte_index(addr))
#define pte_unmap(pte)			__pte_unmap(pte)

#define pte_pfn(pte)		((pte_val(pte) & PHYS_MASK) >> PAGE_SHIFT)
#define pfn_pte(pfn,prot)	__pte(__pfn_to_phys(pfn) | pgprot_val(prot))

#define pte_page(pte)		pfn_to_page(pte_pfn(pte))
#define mk_pte(page,prot)	pfn_pte(page_to_pfn(page), prot)

#define pte_clear(mm,addr,ptep)	set_pte_ext(ptep, __pte(0), 0)

#define pte_none(pte)		(!pte_val(pte))
#define pte_present(pte)	(pte_val(pte) & L_PTE_PRESENT)
#define pte_write(pte)		(!(pte_val(pte) & L_PTE_RDONLY))
#define pte_dirty(pte)		(pte_val(pte) & L_PTE_DIRTY)
#define pte_young(pte)		(pte_val(pte) & L_PTE_YOUNG)
#define pte_exec(pte)		(!(pte_val(pte) & L_PTE_XN))
#define pte_special(pte)	(0)
以上がソースを追うのに必要な情報(だったはず)。

具体的に変換テーブルはどこに..?

見ていて有用なのがデバッグ用の関数やコメント。
Oopsを吐くときに使用されている show_pte()から依存部の実装を追える。

FILE: arch/arm/mm/fault.c
/*
 * This is useful to dump out the page tables associated with
 * 'addr' in mm 'mm'.
 */
void show_pte(struct mm_struct *mm, unsigned long addr)
{
	pgd_t *pgd;

	if (!mm)
		mm = &init_mm;

	printk(KERN_ALERT "pgd = %p\n", mm->pgd);
	pgd = pgd_offset(mm, addr);
	printk(KERN_ALERT "[%08lx] *pgd=%08llx",
			addr, (long long)pgd_val(*pgd));
...
第一レベル変換テーブルが mm_structのメンバpgdであることがわかる。
引数mmがNULLの場合、すなわちタスクのmmが無い状態では、init_mmが基底になっていそうである*1

このinit_mmを探してみると以下に見つかる。

FILE:mm/init-mm.c
struct mm_struct init_mm = {
        .mm_rb          = RB_ROOT,
        .pgd            = swapper_pg_dir,
        .mm_users       = ATOMIC_INIT(2),
        .mm_count       = ATOMIC_INIT(1),
        .mmap_sem       = __RWSEM_INITIALIZER(init_mm.mmap_sem),
        .page_table_lock =  __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
        .mmlist         = LIST_HEAD_INIT(init_mm.mmlist),
        INIT_MM_CONTEXT(init_mm)
};
これ(swapper_pg_dir)の実態はどこにあるのか。
FILE:arch/arm/kernel/head.S
/*
 * swapper_pg_dir is the virtual address of the initial page table.
 * We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must
 * make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect
 * the least significant 16 bits to be 0x8000, but we could probably
 * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
 */

#define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif

#ifdef CONFIG_ARM_LPAE
        /* LPAE requires an additional page for the PGD */
#define PG_DIR_SIZE     0x5000
#define PMD_ORDER       3
#else
#define PG_DIR_SIZE     0x4000
#define PMD_ORDER       2
#endif

        .globl  swapper_pg_dir
        .equ    swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE

        .macro  pgtbl, rd, phys
        add     \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE
        .endm
MMUのテーブルウォークに使われる、レベル1変換テーブルをswapper_pg_dirに格納している。
しかし、2MiB/1stLvとして運用している(前引用のコメントどおり)。
PGD1MiB単位のブロック.. 1MiB x 4Ki個(12bit)のテーブルで 4GiBフルアドレッシング
PTE最終的に4KiBをインデックスして 256個(8bit)テーブルがあれば 1MiBになる。


pgd_offset_k()でインデックスを求めるが、これは2MiB単位でアドレスを求める。
pgdが2MiBになる理由が未だわからんなぁ。
テーブルは作ってしまうのに、どこに属性を置くのか...

タスクがある場合
SMPの場合は cpu固有情報は cpu_なんとか()という関数名で実装されているようである。
第一レベル変換テーブル、すなわちpgd配列の先頭ポインタを取得する関数を見てみると、
以下の実相となっていた。
FILE: arch/arm/include/asm/proc-fns.h
#ifdef CONFIG_ARM_LPAE
#else
#define cpu_get_pgd()   \
        ({                                              \
                unsigned long pg;                       \
                __asm__("mrc    p15, 0, %0, c2, c0, 0"  \
                         : "=r" (pg) : : "cc");         \
                pg &= ~0x3fff;                          \
                (pgd_t *)phys_to_virt(pg);              \
        })
#endif
意訳すると、"Translation Table Base Register 0"を取得する
(=current taskのpgd先頭アドレス(物理)を取得して、論理に変換する)と言えよう。


*1 : タスク生成時のメモリアロケーションを調べておく必要がありますなぁ

で?

MMU faultのハンドラは以下で、割り込みベクタまわりの処理から呼ばれてくる。
FILE: arch/mm/fault.c
static int __kprobes
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
...
	tsk = current;
	mm  = tsk->mm;
...
	/*
	 * If we're in an interrupt or have no user
	 * context, we must not take the fault..
	 */
	if (in_atomic() || !mm)
		goto no_context;
...
	fault = __do_page_fault(mm, addr, fsr, flags, tsk);
....
	if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP | VM_FAULT_BADACCESS))))
		return 0;
...
	__do_user_fault(tsk, addr, fsr, sig, code, regs);
	return 0;

no_context:
	__do_kernel_fault(mm, addr, fsr, regs);
	return 0;

ユーザ空間

static int __kprobes
__do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
		unsigned int flags, struct task_struct *tsk)
{
	struct vm_area_struct *vma;
	int fault;

	vma = find_vma(mm, addr);
	fault = VM_FAULT_BADMAP;
	if (unlikely(!vma))
		goto out;
	if (unlikely(vma->vm_start > addr))
		goto check_stack;

	/*
	 * Ok, we have a good vm_area for this
	 * memory access, so we can handle it.
	 */
good_area:
	if (access_error(fsr, vma)) {
		fault = VM_FAULT_BADACCESS;
		goto out;
	}

	return handle_mm_fault(mm, vma, addr & PAGE_MASK, flags);

check_stack:
	/* Don't allow expansion below FIRST_USER_ADDRESS */
	if (vma->vm_flags & VM_GROWSDOWN &&
	    addr >= FIRST_USER_ADDRESS && !expand_stack(vma, addr))
		goto good_area;
out:
	return fault;
}
ここはユーザ空間でのfaultで、タスクに割り当てられた空間かどうかをチェックしています。
find_vma()がそれで、mm_struct構造体メンバのmmapをスキャンします(curent->vmacache[]を先に見る)。
⇒タスクごとのメモリ管理、割り当てと解放、その他はこの変数を触っている個所を見ればよいと分かりました。

handle_mm_fault()はアーキテクチャに依存しないソースで実装されていますので、まずはここまで。
エラー発生時
SIGSEGVとかですね.. 引数sigに エラー判定結果を入れて呼ばれます。
/*
 * Something tried to access memory that isn't in our memory map..
 * User mode accesses just cause a SIGSEGV
 */
static void
__do_user_fault(struct task_struct *tsk, unsigned long addr,
		unsigned int fsr, unsigned int sig, int code,
		struct pt_regs *regs)

kernel空間

コンテキストが無かったり、ディスパッチ禁止(in_atomic()==true)の場合などに呼ばれます。ね。
/*
 * Oops.  The kernel tried to access some page that wasn't present.
 */
static void
__do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
		  struct pt_regs *regs)
{
	/*
	 * Are we prepared to handle this kernel fault?
	 */
	if (fixup_exception(regs))
		return;

	/*
	 * No handler, we'll have to terminate things with extreme prejudice.
	 */
	bust_spinlocks(1);
	printk(KERN_ALERT
		"Unable to handle kernel %s at virtual address %08lx\n",
		(addr < PAGE_SIZE) ? "NULL pointer dereference" :
		"paging request", addr);

	show_pte(mm, addr);
	die("Oops", regs, fsr);
	bust_spinlocks(0);
	do_exit(SIGKILL);
}
カーネル空間でfaultが起きることは不具合以外にはレアケースだと考えられます。
基本的にカーネル自体はストレートマップされるので。ARMのようにメモリマップドIOで
ioremap()を忘れていたとか、範囲を間違ったとかならありえそうです。

あと、発生が予期できる個所に関しては、fixup_exception()で判定されます。
これは前回の記事へ。。。


ARMの資料をみる

Programmer's manualはぜひとも見ていただきたい。
Linux OSを意識した説明がしっかりと記述されていて、スタートアップコードや本記事の対応・実装説明についても記載があります.
■Cortex-A Series Programmer's manual
8.9.2 Emulation of dirty and accessed bits
Linux makes use of a bit associated with each page which marks whether the page is dirty (the page has been modified by the application and may therefore need to be written from memory to disk when the page is no longer needed).
This is not directly supported in hardware by ARM processors, and must therefore be implemented by kernel software.
When a page is first created, it is marked as read-only.
The first write to such a page (a clean page) will cause an MMU permission fault and the kernel data abort handler will be called.
The Linux memory management code will mark the page as dirty, if the page should indeed be writable, using the function handle_pte_fault() .
The page table entry is modified to make it allow both reads and writes and the TLB entry invalidated from the cache to ensure the MMU now uses the newly modified entry.
The abort handler then returns to re-try the faulting access in the application.
Linuxは、ページがダーティであるか否かをマークし、各ページに対応するビットを利用する。
ダーティである、とは、ページがアプリケーションによって変更されるか、そのページが必要ではなくなったときにメモリからディスクへ書き出す必要があることを示す。
ARMプロセッサでは、直接的にハードウェアでサポートされていない。したがってカーネルソフトウェアで実装される必要がある。
ページが最初に作られたとき、read-onlyとしてマークされる。
そのページ(クリーンなページ)に対して、最初の書き込みは、MMUのpermission faultの要因となり、カーネルのデータアボートハンドラが呼ばれる。
Linuxのメモリ管理コードは、そのページをdirtyと設定します。そのページが書き込み可能であるべきならば、handle_pte_fault()を使う。
ページテーブルエントリーは、読み書き可能に変更される。そして、TLBエントリは、MMUが今新しく変更されたエントリを使うため、キャッシュから無効化される。
その後、アボートハンドラは、アプリケーション内でfaultアクセスした命令を再実行するため、リターンする。
*訳注
handle_te_fault()は最終的によばれれるもので、PUD, PMDが無ければ取得するし、エラー判定になれば呼ばれず返る。

2014/11/26(水)[ARM][Linux]copy_{from|to}_user()

Linux Advent Calendar 2014の19日目に登録させていただきました。

copy_to_user()

kernel空間からusesr空間への安全なメモリコピー、と見てもらえれば良いでしょう。
単純なメモリコピーは、memcpy()を使いますが、これはユーザ空間のアドレスを
指定できることに意義があるもの。

ユーザ空間のメモリは必ずしも物理メモリに割りつけられているとは限らない。
まぁこれだけではないので、swap未使用であったりメモリが多くても使いましょう。
NAME
 copy_to_user - Copy a block of data into user space.

SYNOPSIS
 unsigned long copy_to_user(void __user * to, const void * from, unsigned long n);

ARGUMENTS
 to
   Destination address, in user space.

 from
   Source address, in kernel space.

 n
   Number of bytes to copy.

CONTEXT
 User context only. This function may sleep.

DESCRIPTION
 Copy data from kernel space to user space.
 Returns number of bytes that could not be copied. On success, this will be zero.


実装を追う

実際にコードを追った順序と記載順序が異なるので、変な感じです。

関数の実態は以下にあります。
アライメントを考慮した最適な転送を行うためのテンプレでコードが書かれており、
そこで使用するデータ転送の実態をcopy_to_userとcopy_from_userでマクロ定義し、
同一templateでコードを生成させています。
FILE: arch/arm/lib/copy_to_user.S
FILE: arch/arm/lib/copy_template.S

で、ここからが本題。strusrマクロでは、abort引数がついています。
これが何しているのか、何となく想像はつきますが、理解できなかった。
バイナリを逆アセンブルすると普通のstr命令が並ぶだけで、memcpyとの差異が判らず。
実コードと併せて定数テーブルを作っていることに気がつき、
このセクション名で検索したところ仕組みがわかったという流れでした。

FILE: copy_to_user.S
	.macro str1w ptr reg abort
	strusr	\reg, \ptr, 4, abort=\abort
	.endm
FILE: archinc/asm/assembler.h
	.macro	strusr, reg, ptr, inc, cond=al, rept=1, abort=9001f
	usracc	str, \reg, \ptr, \inc, \cond, \rept, \abort
	.endm

	.macro	usracc, instr, reg, ptr, inc, cond, rept, abort, t=TUSER()
	.rept	\rept
9999:
	.if	\inc == 1
	\instr\cond\()b\()\t \reg, [\ptr], #\inc
	.elseif	\inc == 4
	\instr\cond\()\t \reg, [\ptr], #\inc
	.else
	.error	"Unsupported inc macro argument"
	.endif

	.pushsection __ex_table,"a"
	.align	3
	.long	9999b, \abort
	.popsection
	.endr
	.endm

usracc str, \reg, \ptr, \inc(=4), \cond(=none,al), \rept(=none,1), \abort(=pop)
	stral reg, [ptr], #inc
	.pushsection __ex_table,"a"
	.align	3
	.long	9999b, \abort

セクション "__ex_table"に、{exceptionが発生するアドレス、返りアドレス}を設定する。
ldscriptにて、同名のセクションを定義、その前後のアドレスをを拾えるようにしてある。
FILE: /kernel/linux-stable/arch/arm/kernel/vmlinux.lds.S
	. = ALIGN(4);
	__ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {
		__start___ex_table = .;
#ifdef CONFIG_MMU
		*(__ex_table)
#endif
		__stop___ex_table = .;

テーブルの先頭が変数名として判ったので、コレを探す。
これを参照しているのは、以下の関数。
/* Given an address, look for it in the exception tables. */
const struct exception_table_entry *search_exception_tables(unsigned long addr)
さらにこれを使っているところを探すと、アーキ依存部のpage fault処理部分が見つかる。

FILE: kernel/linux-stable/arch/arm/mm/extable.c
int fixup_exception(struct pt_regs *regs)
{
	const struct exception_table_entry *fixup;

	fixup = search_exception_tables(instruction_pointer(regs));
	if (fixup) {
		regs->ARM_pc = fixup->fixup;
#ifdef CONFIG_THUMB2_KERNEL
		/* Clear the IT state to avoid nasty surprises in the fixup */
		regs->ARM_cpsr &= ~PSR_IT_MASK;
#endif
	}

	return fixup != NULL;
}
さらにさらに、これを使っているところを探すと、
アーキ依存部のpage fault処理部分が見つかる。


FILE: kernel/linux-stable/arch/arm/mm/fault.c
/*
 * Oops.  The kernel tried to access some page that wasn't present.
 */
static void
__do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
		  struct pt_regs *regs)
{
	/*
	 * Are we prepared to handle this kernel fault?
	 */
	if (fixup_exception(regs))
		return;
ここに来れば faultが発生しても、Oopsを吐かずに指定されたアドレスへ
帰って行くという仕組みになっている。

copy_to_user()に関しては、エラーハンドリングは特に行っていない。
失敗すればコピーを中断して返る、という実装になっている。
そのため、返値はコピーバイト数の残り、ということにしたのであろう。

で、この__do_kernel_fault()はどこから来たのかなぁと見ていくと、
fault handlerのほうへと流れ着くことになる。
ここらへんまできて、ちょっと深みにはまりそうだな、と感じたので
別記事にすることにします。

FILE: kernel/linux-stable/arch/arm/mm/fault.c
static int __kprobes
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)

2014/10/28(火)glibcのビルド

逆アセンブルしてもソースコード中の関数をみても一致しない。
なぜだろうか・・・、ということで少し探ってみた時のメモ。
解決に至っていないので、そのものズバリが欲しい人は回れ右でお願いいたします・・・。
情報提供、誤り指摘はありがたく頂戴させていただきます。

ビルドされたログを見る

ct-ngにビルドしてもらった時のログから、1ファイルのコマンドラインを引っ張ってみる。
[ALL  ] 
( echo '#define SYSCALL_NAME close';
	echo '#define SYSCALL_NARGS 1';
	echo '#define SYSCALL_SYMBOL __libc_close';
	echo '#define SYSCALL_CANCELLABLE 1';
	echo '#include <syscall-template.S>';
	echo 'weak_alias (__libc_close, __close)';
	echo 'libc_hidden_weak (__close)';
	echo 'weak_alias (__libc_close, close)';
	echo 'libc_hidden_weak (close)';
	) \
 | arm-linaro-linux-gnueabi-gcc -c 
 -I../include 
-I/PATH/TO/MYDIR/_build/arm-linaro-linux-gnueabi/build/build-libc-final/io 
-I/PATH/TO/MYDIR/_build/arm-linaro-linux-gnueabi/build/build-libc-final 
-I../ports/sysdeps/arm/elf 
-I../ports/sysdeps/unix/sysv/linux/arm/eabi/nptl 
-I../ports/sysdeps/unix/sysv/linux/arm/eabi 
-I../ports/sysdeps/unix/sysv/linux/arm/nptl 
-I../ports/sysdeps/unix/sysv/linux/arm 
-I../nptl/sysdeps/unix/sysv/linux 
-I../nptl/sysdeps/pthread 
-I../sysdeps/pthread 
-I../ports/sysdeps/unix/sysv/linux 
-I../sysdeps/unix/sysv/linux 
-I../sysdeps/gnu 
-I../sysdeps/unix/common 
-I../sysdeps/unix/mman 
-I../sysdeps/unix/inet 
-I../nptl/sysdeps/unix/sysv 
-I../ports/sysdeps/unix/sysv 
-I../sysdeps/unix/sysv 
-I../ports/sysdeps/unix/arm 
-I../nptl/sysdeps/unix 
-I../ports/sysdeps/unix 
-I../sysdeps/unix 
-I../sysdeps/posix 
-I../ports/sysdeps/arm/eabi 
-I../ports/sysdeps/arm/nptl 
-I../ports/sysdeps/arm 
-I../sysdeps/wordsize-32 
-I../sysdeps/ieee754/flt-32 
-I../sysdeps/ieee754/dbl-64 
-I../sysdeps/ieee754 
-I../sysdeps/generic/elf 
-I../sysdeps/generic 
-I../nptl 
-I../ports  
-I.. 
-I../libio 
-I. -nostdinc
 -isystem /PATH/TO/MYDIR/_build/arm-linaro-linux-gnueabi/buildtools/lib/gcc/arm-linaro-linux-gnueabi/4.7.4/include
 -isystem /PATH/TO/MYDIR/_build/arm-linaro-linux-gnueabi/buildtools/lib/gcc/arm-linaro-linux-gnueabi/4.7.4/include-fixed
 -isystem /opt/export/V980_cam/repo/_master_m8m/hosttool/arm-linaro-linux-gnueabi/arm-linaro-linux-gnueabi/sysroot/usr/include
 -D_LIBC_REENTRANT
 -include ../include/libc-symbols.h
 -DASSEMBLER
   -Wa,--noexecstack  
 -o /PATH/TO/MYDIR/_build/arm-linaro-linux-gnueabi/build/build-libc-final/io/close.o
 -x assembler-with-cpp -
 -MD
 -MP
 -MF /PATH/TO/MYDIR/_build/arm-linaro-linux-gnueabi/build/build-libc-final/io/close.o.dt
 -MT
 /PATH/TO/MYDIR/_build/arm-linaro-linux-gnueabi/build/build-libc-final/io/close.o
ソースコードだけ見ててもアカンやつや...
というかむしろソースコードないぞおい.
echoコマンドで現存するシステムコールはソースコードを生成してコンパイルしちゃうのかなぁ.
えぐいなぁ...

glibcじゃなくなるけど, eglibcの場合は 以下のソースをincludeして コードが生成されちゃう模様.
src/eglibc-2_13/sysdeps/unix/syscall-template.S
src/eglibc-2_13/ports/sysdeps/unix/sysv/linux/arm/nptl/sysdep-cancel.h
※nptl/cancel可能を前提として.

2014/10/08(水)memory test

メモリテストプログラム

いわゆるPCのmemtestがそう。
PCとは異なるアーキテクチャでも、特に組込み危機でも必要となるでしょう。
この記事は、メモ代わりに記載するだけで本体に価値はありません・・・。
(オフラインで資料を作る際に参考にさせていただいた時のメモ)

メモリテストの目的や達成目標を明確にすることで、必要十分なものを選択しましょう。
(過去の踏襲…とか、非論理的なシゴトは決してしないように、なぁ。)

参考

メモリテストに関するよい記事

http://www.barrgroup.com/Embedded-Systems/How-To/Memory-Test-Suite-C

掲示板には20年の職人が来ている

http://stackoverflow.com/questions/3729544/free-implementation-of-march-memory-testing-algorithm
>Memory testing with cache
>Permalink Submitted by krishnakoyalmannam on Thu, 2011-05-19 02:00.
When you are testing external DDR2 memory on a multicore system,
 with a shared Level 2 Cache,
 each core could be assigned a dedicated section of memory to test and all the cores memory tests in parallel.
This causes the L2 cache to result in more cache miss than cache hit,
 thereby resulting in increased DRAM bus transactions,
 which could be very useful during HW Board BringUp.
And yes, the ground bounce and cross-talk tests do check for signal integrity problems. There is also MARCH-B and MARCH-C memory testing algorithms that sweep memory with random patterns in one direction and sweep in the reverse direction, so, a cache flush would be required before starting the reverse sweep. And finally, an XOR test to uncover memory cell faults, would make the suite of memory tests quite comprehensive providing greater coverage.

マーチアルゴリズム:LSIテスト用だなぁ

http://www.ece.uc.edu/~wjone/
http://www.ece.uc.edu/~wjone/Memory.pdf

2014/10/08(水)procfsの辿り方

kernel coding:

http://www.linuxplanet.org/blogs/?cat=4558
http://tuxthink.blogspot.jp/2013/10/creating-read-write-proc-entry-in.html

proc_create("name", 0, NULL, &proc_fops);

Ex. fs/proc/cmdline.c
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int cmdline_proc_show(struct seq_file *m, void *v)
{
        seq_printf(m, "%s\n", saved_command_line);
        return 0;
}

static int cmdline_proc_open(struct inode *inode, struct file *file)
{
        return single_open(file, cmdline_proc_show, NULL);
}

static const struct file_operations cmdline_proc_fops = {
        .open           = cmdline_proc_open,
        .read           = seq_read,
        .llseek         = seq_lseek,
        .release        = single_release,
};

static int __init proc_cmdline_init(void)
{
        proc_create("cmdline", 0, NULL, &cmdline_proc_fops);
        return 0;
}
module_init(proc_cmdline_init);
非常に判りよい例.. ;)
OK キャンセル 確認 その他