proc 파일 시스템 준비
다음 그림은 proc 파일 시스템의 초기화 및 마운트에 대한 함수 처리 흐름이다.
proc_root_init()
fs/proc/root.c
void __init proc_root_init(void) { int err; proc_init_inodecache(); err = register_filesystem(&proc_fs_type); if (err) return; proc_self_init(); proc_thread_self_init(); proc_symlink("mounts", NULL, "self/mounts"); proc_net_init(); #ifdef CONFIG_SYSVIPC proc_mkdir("sysvipc", NULL); #endif proc_mkdir("fs", NULL); proc_mkdir("driver", NULL); proc_mkdir("fs/nfsd", NULL); /* somewhere for the nfsd filesystem to be mounted */ #if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE) /* just give it a mountpoint */ proc_mkdir("openprom", NULL); #endif proc_tty_init(); proc_mkdir("bus", NULL); proc_sys_init(); }
마운트하여 사용할 수 있도록 커널 내부에 proc 루트 파일 시스템을 생성하고 초기화한다.
- 코드 라인 5에서 proc_inode 구조체를 자주 사용하므로 이의 kmem 캐시를 준비한다.
- 코드 라인 6~7에서 “proc” 파일 시스템을 등록한다. 파일 시스템들은 name을 유니크 키로 사용하는 전역 file_systems(next로 연결된다)에 단방향으로 등록순으로 연결된다.
- 코드 라인 9에서 inode용 정수 id를 발급받아 self_inum에 대입한다.
- inode용 IDA는 전역 proc_inum_ida를 통해 관리된다.
- 코드 라인 10에서 inode용 정수 id를 발급받아 thread_self_inum에 대입한다.
- 코드 라인 11에서 “self/mounts”를 가리키도록 mounts라는 이름으로 스태틱 링크를 /proc에 생성한다.
- 예) # ls /proc/mounts -la
lrwxrwxrwx 1 root root 11 Apr 16 16:55 /proc/mounts -> self/mounts
- 예) # ls /proc/mounts -la
- 코드 라인 13에서 “self/net”를 가리키도록 net라는 이름으로 스태틱 링크를 /proc에 생성한다. 그런 후 네트워크 네임스페이스 서브시스템에 등록한다.
- 예) # ls /proc/net -la
lrwxrwxrwx 1 root root 8 Apr 16 16:56 /proc/net -> self/net
- 예) # ls /proc/net -la
- 코드 라인 15~17에서 /proc/sysvipc 디렉토리를 생성한다.
- 코드 라인 18에서 /proc/fs 디렉토리를 생성한다.
- 코드 라인 19에서 /proc/driver 디렉토리를 생성한다.
- 코드 라인 20에서 /proc/nfsd 디렉토리를 생성한다.
- 코드 라인 21~24에서 /proc/openprom 디렉토리를 생성한다.
- 코드 라인 25에서 /proc/tty 디렉토리를 생성하고 그 하위 디렉토리에 다음을 추가로 생성한다.
- /proc/tty/ldisc 및 /proc/tty/driver 디렉토리를 생성한다.
- /proc/drivers 및 /proc/ldiscs 라는 이름으로 파일을 생성하고 proc 동작이 되도록 파일 오퍼레이션을 연결한다.
- 코드 라인 26에서 /proc/bus 디렉토리를 생성한다.
- 코드 라인 27에서 /proc/sys 디렉토리를 생성하고 proc 디렉토리 오페레이션이 되도록 연결한다. 그런 후 그 하위 디렉토리에 다음 디렉토리들을 추가로 생성하고 해당 디렉토리 내에 관련 proc 파일들을 연결한다.
- /proc/sys/kernel 디렉토리 및 하위 proc 파일들
- /proc/sys/vm 디렉토리 및 하위 proc 파일들
- /proc/sys/fs 디렉토리 및 하위 proc 파일들
- /proc/sys/debug 디렉토리 및 하위 proc 파일들
- /proc/sys/dev 디렉토리 및 하위 proc 파일들
register_filesystem()
fs/filesystems.c
/** * register_filesystem - register a new filesystem * @fs: the file system structure * * Adds the file system passed to the list of file systems the kernel * is aware of for mount and other syscalls. Returns 0 on success, * or a negative errno code on an error. * * The &struct file_system_type that is passed is linked into the kernel * structures and must not be freed until the file system has been * unregistered. */ int register_filesystem(struct file_system_type * fs) { int res = 0; struct file_system_type ** p; BUG_ON(strchr(fs->name, '.')); if (fs->next) return -EBUSY; write_lock(&file_systems_lock); p = find_filesystem(fs->name, strlen(fs->name)); if (*p) res = -EBUSY; else *p = fs; write_unlock(&file_systems_lock); return res; } EXPORT_SYMBOL(register_filesystem);
file_system_type 구조체를 전역 &file_systems의 마지막에 추가한다. name(이름)에 “.”이 있거나 이미 사용한 이름이 있는 경우 에러가 반환된다.
아래 그림은 rpi2 시스템에 등록되는 파일 시스템들을 보여준다. 그 외에 arm64에서 사용되는 hgetlbfs 및 임베디드 시스템의 플래시 파일 시스템으로 자주 사용되는 jffs2 파일 시스템도 별도로 표기하였다. (그 외의 파일 시스템은 자주 사용하지 않으므로 생략)
proc 파일 시스템 마운트
proc_mount()
fs/proc/root.c
static struct dentry *proc_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { int err; struct super_block *sb; struct pid_namespace *ns; char *options; if (flags & MS_KERNMOUNT) { ns = (struct pid_namespace *)data; options = NULL; } else { ns = task_active_pid_ns(current); options = data; if (!capable(CAP_SYS_ADMIN) && !fs_fully_visible(fs_type)) return ERR_PTR(-EPERM); /* Does the mounter have privilege over the pid namespace? */ if (!ns_capable(ns->user_ns, CAP_SYS_ADMIN)) return ERR_PTR(-EPERM); } sb = sget(fs_type, proc_test_super, proc_set_super, flags, ns); if (IS_ERR(sb)) return ERR_CAST(sb); if (!proc_parse_options(options, ns)) { deactivate_locked_super(sb); return ERR_PTR(-EINVAL); } if (!sb->s_root) { err = proc_fill_super(sb); if (err) { deactivate_locked_super(sb); return ERR_PTR(err); } sb->s_flags |= MS_ACTIVE; } return dget(sb->s_root); }
커널에 등록된 proc 파일 시스템을 마운트한다.
- 코드 라인 9~22에서 다음 커널 경로를 통해 호출되어 MS_KERNMOUNT 플래그가 붙은 경우 인자로 전달 받은 ns(네임 스페이스)를 알아온다. 그 외의 경우 태스크에서 사용되는 ns를 알아온다.
- mq_init_ns(), pid_ns_prepare_proc() 및 init_hugetlbfs_fs() 함수를 사용하는 경우 다음 경로를 통해 ns를 포함하여 커널 마운트 플래그가 사용된다.
- ns 사용: kern_mount_data() -> vfs_kern_mount() -> mount_fs()
- simple_pin_fs() 함수를 사용하는 경우 다음 커널 경로를 통해 ns에 null이 대입된 상태로 커널 마운트 플래그가 사용된다.
- ns=null: vfs_kern_mount() -> mount_fs()
- mq_init_ns(), pid_ns_prepare_proc() 및 init_hugetlbfs_fs() 함수를 사용하는 경우 다음 경로를 통해 ns를 포함하여 커널 마운트 플래그가 사용된다.
- 코드 라인 24~26에서 인자로 요청한 동일한 파일 시스템 타입의 수퍼 블럭들이 proc_test_super() 함수의 결과와 동일하면 proc_set_super() 함수를 호출한다.
- proc_test_super() 함수를 통해 ns와 동일한 수퍼블럭인지 체크
- proc_set_super() 함수를 통해 해당 수퍼 블럭을 가상 파일 시스템에 사용하는 anon 타입으로 할당하고 수퍼 블럭의 s_fs_info에 pid 값을 지정한다.
- 코드 라인 28~31에서 커널 마운트가 아닌 경우 인자로 전달 받은 옵션 문자열을 파싱한다. proc 파일 시스템은 다음 두 개의 옵션을 파싱하여 사용한다.
- “hidepid=<0~2>”
- 0: 모든 유저에게 /proc/<pid>/*이 다 보이고 읽을 수 있는 커널 디폴트 설정이다.
- 1: 모든 유저에게 /proc/<pid>/*이 다 보이지만 owner 유저만 읽을 수 있는 설정이다.
- 2: owner 유저만 /proc/<pid>/*이 보이고 읽을 수 있는 설정이다.
- “gid=XXX”
- hidepid=0을 사용하면서 지정된 그룹이 모든 프로세스 정보를 수집할 수 있게 한다.
- 참고:
- “hidepid=<0~2>”
- 코드 라인 33~41에서 수퍼 블록에서 루트가 지정되지 않은 루트 inode를 생성하고 초기화한 후 지정한다.
- 코드 라인 43에서 루트 inode에 대한 참조 카운터를 증가시키고 리턴한다.
다음 그림은 proc_mount() 함수에 도달하는 과정과 그 이후의 처리를 보여준다.
참고
- proc_root_init() | 문c – 현재글
- proc interface & seq_file | 문c
- mount(8) – Linux manual page | man7.org