Linux 커널 - 시스템 호출 구현 - 페이지 테이블 탐색

Linux 커널 - 시스템 호출 구현 - 페이지 테이블 탐색

저는 Linux 커널에 대해 배우려고 노력 중이며 프로세스의 페이지 테이블에서 페이지를 읽을 목적으로 4.15.0+ Linux 커널에서 두 개의 시스템 호출을 구현해야 하는 연습을 발견했습니다. 목표는 첫 번째 시스템 호출이 페이지 수를 계산하고 두 번째 시스템 호출이 메모리에 존재하고 읽기 전용 모드에 있는 모든 페이지를 1로 나타내는 비트맵을 검색하는 것입니다.

온라인에서 몇 가지 조사를 한 후 내린 결론은 다음과 같습니다.

struct task_struct {
   ....
    struct list_head pg_list;
    unsigned long count;
}

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/string.h>
#include <linux/mm.h>

struct page_list{
    size_t num_page;
    struct list_head list;
};


asmlinkage long sys_get(const char *proc_name const size_t name_len){
    char p_name[name_len+1];
    struct task_struct *task;
    struct vm_area_struct *vma = NULL;
    unsigned long vpage;
    unsigned long count = 0;
        
    if (copy_from_user(p_name, proc_name, name_len)) return -EFAULT;
    p_name[name_len] = '\0';
    
    for_each_process(task){
        if(strcmp(task->comm, p_name) == 0) {
            INIT_LIST_HEAD(&task->pg_list);

            if (task->mm && task->mm->mmap) {
                for (vma = task->mm->mmap; vma; vma = vma->vm_next) {
                    for (vpage = vma->vm_start; vpage < vma->vm_end; vpage += PAGE_SIZE) {
                        
                        pgd_t *pgd = pgd_offset(task->mm, vpage);
                        if (pgd_none(*pgd) || pgd_bad(*pgd)) continue;

                        p4d_t *p4d = p4d_offset(pgd, vpage);
                        if (p4d_none(*p4d) || p4d_bad(*p4d)) continue;

                        pud_t *pud = pud_offset(p4d, vpage);
                        if (pud_none(*pud) || pud_bad(*pud)) continue;

                        pmd_t *pmd = pmd_offset(pud, vpage);
                        if (pmd_none(*pmd) || pmd_bad(*pmd)) continue;

                        pte_t *pte = pte_offset_map(pmd, vpage);

                        if (pte_present(*pte) && !pte_write(*pte)) {
                            struct page_list *el = kmalloc(sizeof(*el), GFP_KERNEL);
                            if (!el) return -ENOMEM;
                            el->num_page = count;
                            list_add(&el->list, &our_task->pg_list);
                        }
                        count++;
                    }
                }
            }
        }
    }
    return count;
}

asmlinkage long sys_get(char *bitmap, const char *proc_name, const size_t name_len){    
    struct list_head *i;
    struct list_head *n;
    struct page_list *el;
    struct task_struct *task;
    char p_name[name_len+1];
        
    if (copy_from_user(p_name, proc_name, name_len)) return -EFAULT;
    p_name[name_len] = '\0';
       
    if (bitmap == NULL) {
        return -ENOENT;
    }
    
    for_each_process(task){
        if(strcmp(task->comm, p_name) == 0){
            list_for_each_safe(i, n, &task->pg_list){
                el = list_entry(i ,struct page_list, list);
                bitmap[el->num_page] = 1;
                list_del(i);
                kfree(el);
            }
            kfree(&task->pg_list);
        }
    }
    return 0;
}

이는 예상대로 작동하지 않으며 때로는 내가 잘 이해할 수 없는 이유로 커널 패닉을 유발합니다. 커널 코드로 작업하는 것은 이번이 처음입니다. 제가 갖고 있는 몇 가지 구체적인 질문은 다음과 같습니다. 프로세스에 해당하는 모든 페이지를 반복하는 효율적인 방법입니까? 읽기 전용 현재 페이지를 찾는 데 사용하는 매크로가 올바른가요(x86 - 32 아치 전용)? 내 결과를 char *bitmap에 쓰는 올바른 방법은 무엇입니까? 결과는 사용자 공간에서 왔으며 단지 액세스하는 것이 잘못되었기 때문입니다. 마지막으로 커널 패닉은 제가 메모리를 처리하는 방식에서 발생하는데, 제가 뭘 잘못하고 있는지 잘 모르겠습니다. 어떤 피드백이라도 대단히 감사하겠습니다!

관련 정보