-
Notifications
You must be signed in to change notification settings - Fork 0
Process Thread Memory & Libraries kernel userspace
A synthesis of how a process/thread is represented, how fork and
pthread_create differ, how libraries and files are wired, and the kernel
structs behind it all. Companion to the mb_labs; each section notes the lab
that exercises it.
fork() pthread_create()
| glibc | glibc
v v
clone(SIGCHLD) clone(CLONE_VM | CLONE_FILES | CLONE_FS |
(no sharing flags) CLONE_SIGHAND | CLONE_THREAD |
| CLONE_SETTLS | ...)
v v
kernel: copy_process() -> new task_struct; per resource: SHARE or COPY
copy_process()
/* simplified from kernel/fork.c */
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
if (clone_flags & CLONE_VM) {
mm = oldmm; /* SHARE: same mm_struct, bump refcount (pthread) */
atomic_inc(&mm->mm_users);
} else {
mm = dup_mm(tsk, oldmm); /* COPY: new mm_struct, COW page tables (fork) */
}
tsk->mm = mm;
}
Rule in copy_process(): flag set (1) -> share the pointer; flag clear (0) -> copy.
| resource (task_struct field) | flag | fork (=0) | pthread (=1) |
|---|---|---|---|
address space mm
|
CLONE_VM | copy (new mm, COW) | share (same mm) |
fd table files
|
CLONE_FILES | copy | share |
cwd/root fs
|
CLONE_FS | copy | share |
signal handlers sighand
|
CLONE_SIGHAND | copy | share |
thread group / tgid
|
CLONE_THREAD | new tgid | same tgid |
| TLS (TPIDR_EL0) | CLONE_SETTLS | inherited | set per-thread |
- fork: new task_struct, own mm (copied, COW), new tgid -> a PROCESS.
- pthread: new task_struct, shared mm (same pointer), same tgid -> a THREAD.
- The kernel schedules both identically as tasks; everything else falls out of the
mmdecision.
Identity: getpid() == kernel tgid (process); gettid() == kernel pid (thread).
The word "pid" flips meaning between layers. (Lab: procthread_lab.)
task_struct -- ONE per thread (the schedulable entity)
|- state, flags(PF_KTHREAD), pid, tgid, comm[16], real_parent, group_leader
|- stack ------> kernel stack
|- mm ---------> mm_struct (address space; NULL for kthreads)
|- active_mm --> installed mm (kthreads borrow one)
|- files ------> files_struct (fd table)
|
(mm) mm_struct -- ONE per process (shared by its threads)
|- mmap ------> vm_area_struct (head of the VMA list)
|- mm_rb -----> rbtree of the VMAs (fast find_vma)
|- pgd -------> page tables (-> TTBR0)
|- mm_users --> # threads sharing it
|- map_count, mmap_sem, start_code/start_stack ...
|
(mmap) vm_area_struct -- ONE per memory region
|- vm_start, vm_end the VA range [start,end)
|- vm_flags VM_READ/WRITE/EXEC/SHARED
|- vm_page_prot -> PTE permission bits
|- vm_file backing struct file (NULL = anonymous)
|- vm_pgoff offset into vm_file
|- vm_ops .fault/.open/.close handlers
|- vm_next/prev, vm_rb, vm_mm
-
task_struct = the thread: identity + pointers.
mmNULL for kthreads. -
mm_struct = the address space: page tables (
pgd) + the list of VMAs (mmap). Shared by threads (CLONE_VM), copied by fork. -
vm_area_struct (VMA) = one contiguous region with uniform perms/backing.
A
.so= several VMAs (text r-xp shared, data rw-p COW). -
files_struct = the fd table (array of
struct file *). -
struct file = one open-file instance (offset, flags,
f_op); your char devices'.read/.write/.mmapare reached through it.
Labs: pid/tgid/comm/mm -> procthread_lab,current_lab,findtask_lab;
mm->mmap/VMAs -> vma_lab; pgd/page tables -> ptwalk_lab; vm_ops/.mmap ->
mmap_lab; struct file/file_operations -> procfs/sysfs/mmap/procthread labs.
static .a
|
dynamic .so
|
|
|---|---|---|
| linked | build time (copied into binary) | runtime (ld.so mmaps it) |
| binary size | larger | smaller |
| memory across processes | own copy each | ONE shared RO-text copy |
| fix the lib | relink everything | drop-in, all benefit |
A .so's read-only text is mapped shared across every process using it -> one
physical copy via the page cache. Writable data is COW (per-process).
| userspace | kernel analog |
|---|---|
static .a in the binary |
built-in (obj-y -> vmlinux) |
dynamic .so via ld.so |
module .ko via insmod (linked vs kernel symtab) |
| libc: printf/malloc | kernel: printk/kmalloc (EXPORT_SYMBOL) |
| ldd | modinfo/depmod |
A .ko resolves undefined symbols against the kernel symbol table (only
EXPORT_SYMBOLed ones) + loaded modules; CRC (modversions) guards the ABI.
"Unknown symbol" = the needed export isn't present (e.g. IRQ_SIM before built in).
Bridge: the vDSO (linux-vdso.so.1) is a kernel-provided .so mapped into
every process for fast syscalls.
task_struct has no "library" concept. ld.so mmap()s the .so, creating
file-backed VMAs in the process's mm:
Process A Process B
mm_A (own VMA list) mm_B (own VMA list)
libc VMA_A vm_start=0x7fAA.. libc VMA_B vm_start=0x7fBB.. (ASLR: may differ)
vm_file --\ vm_file --\
\----------- struct file / inode: /lib/libc.so ----------/
|
v
page cache: libc text pages (ONE physical copy)
/ \
A page tables B page tables (map to SAME phys pages)
- The VMA is per-process (own descriptor, own virtual address;
.somust be PIC so any load address works). The file + physical pages are shared. - Execution is demand paging: PC jumps into the text VMA -> page fault ->
find_vmasees a file-backed VMA -> read the page fromvm_filevia the page cache -> shared across all mappers. (Lab:ptwalk_labwalks these page tables.) - Symbols/relocations (PLT/GOT) are entirely userspace
ld.sowork; the kernel only ever sees file-backed memory mappings.
Threads vs processes here: threads share the mm -> the same VMA list and same
virtual addresses (one mapping). Processes have separate mms -> separate VMAs
that merely point their vm_file at the same file. Both share the .so's
physical pages; only threads share the mapping itself.
userspace: fd = open("/dev/x"); write(fd, ...)
| (fd = index)
v
task_struct.files -> files_struct (the fd TABLE: array of struct file*)
fd[3] -> struct file (one open: offset, flags, f_op)
f_op -> file_operations (.open/.read/.write/.mmap)
|
v
kernel: YOUR handler runs (procthread_lab pt_write, mmap_lab .mmap)
struct file -> inode (the file) -> dentry (its name) [VFS]
-
fd is just an index into the process's
files_struct. - The fd table is the
filesrow of the fork/pthread table: threads share onefiles_struct; fork copies it (but entries point at the samestruct file, so inherited fds share the offset). - Every char-device lab plugs into this VFS path via the
file_operationsit registers.
A process is a task_struct whose mm points to an mm_struct (page
tables + a list of vm_area_struct regions). fork copies the mm (own
address space, new tgid -> process); pthread_create shares it (same mm,
same tgid -> thread). A shared library is not referenced as a "library" —
ld.so mmaps it into file-backed VMAs (vm_file -> the .so), so each
process has its own mapping but they all fault onto the same page-cache pages.
Files reach kernel code through the VFS: an fd indexes files_struct -> a
struct file -> your file_operations. Userspace uses static (.a, copied
in) or dynamic (.so, shared) libs; the kernel uses built-in (obj-y) or
modules (.ko, linked against exported symbols) — no libc. The kernel
schedules threads and processes identically as tasks; every difference you
observe flows from whether the mm (and tgid) is shared or copied.