Sisteme de Operare - Partea 1
Curs Avansat de Sisteme de Operare — Partea I¶
Kernel-ul Linux: Arhitectură, Procese, Memorie și Sincronizare¶
Cuprins — Partea I¶
- Introducere în kernel-ul Linux
- Spațiul kernel vs. spațiul utilizator
- Pornirea kernel-ului și inițializarea sistemului
- Gestiunea proceselor și planificarea (Scheduling)
- Apeluri de sistem (System Calls)
- Gestiunea memoriei
- Sistemul de fișiere virtual (VFS)
- Mecanisme de sincronizare în kernel
- Întreruperi, Softirqs, Tasklets și Workqueues
- Temporizatoare și gestiunea timpului
Partea a II-a (separată): Module kernel, drivere de dispozitiv (char, block, platform, device tree, bus model), gestiunea I/O, networking stack, debugging, tracing și securitate.
1. Introducere în kernel-ul Linux¶
1.1 Ce este un kernel?¶
Kernel-ul este stratul software fundamental care rulează direct pe hardware, mediind între aplicații și resursele fizice ale sistemului. El abstractizează hardware-ul oferind interfețe uniforme și gestionează accesul concurent la resurse.
┌─────────────────────────────────────────────────┐
│ Aplicații utilizator │
│ (bash, firefox, gcc, python, server web) │
├─────────────────────────────────────────────────┤
│ Biblioteci (glibc, libm, libpthread) │
├──────────────────────┬──────────────────────────┤
│ System Call │ │
│ Interface │ VDSO / vsyscall │
╞══════════════════════╪══════════════════════════╡
│ │ │ ← Granița kernel
│ KERNEL LINUX │
│ │
│ ┌──────────┐ ┌────────────┐ ┌───────────────┐ │
│ │ Process │ │ Memory │ │ VFS │ │
│ │ Scheduler│ │ Management │ │ (Virtual │ │
│ │ │ │ │ │ Filesystem) │ │
│ └──────────┘ └────────────┘ └───────────────┘ │
│ ┌──────────┐ ┌────────────┐ ┌───────────────┐ │
│ │ Network │ │ Device │ │ IPC │ │
│ │ Stack │ │ Drivers │ │ (Pipes, Shmem,│ │
│ │ │ │ │ │ Signals) │ │
│ └──────────┘ └────────────┘ └───────────────┘ │
│ ┌──────────────────────────────────────────┐ │
│ │ Arch-specific code (x86, ARM, RISC-V) │ │
│ └──────────────────────────────────────────┘ │
├─────────────────────────────────────────────────┤
│ HARDWARE │
│ CPU, RAM, Disk, Network, GPU, Periferice │
└─────────────────────────────────────────────────┘
1.2 Tipuri de arhitecturi de kernel¶
| Tip | Descriere | Exemple |
|---|---|---|
| Monolitic | Tot kernel-ul (drivere, FS, networking) rulează în kernel space, într-un singur spațiu de adresare | Linux, FreeBSD, OpenBSD |
| Microkernel | Doar funcționalități minime în kernel (IPC, scheduling); driverele și FS rulează în userspace | MINIX 3, QNX, seL4, Zephyr (hibrid) |
| Hibrid | Nucleu monolitic cu elemente de microkernel | Windows NT, macOS (XNU) |
| Exokernel | Kernel minimal, aplicațiile gestionează resursele direct | MIT Exokernel (academic) |
Linux este monolitic modular: codul rulează în kernel space pentru performanță, dar poate fi extins dinamic cu module încărcabile (LKM) fără a recompila sau reporni kernel-ul.
1.3 Structura codului sursă Linux¶
linux/
├── arch/ Cod specific arhitecturii (x86, arm, arm64, riscv)
│ ├── x86/
│ │ ├── boot/ Codul de boot (setup, decompresie)
│ │ ├── kernel/ Entry points, traps, syscalls
│ │ ├── mm/ Paginare, TLB specifice x86
│ │ └── include/ Headere specifice x86
│ └── arm64/
│ ├── boot/
│ ├── kernel/
│ └── mm/
├── block/ Subsistemul block I/O
├── crypto/ Algoritmi criptografici
├── drivers/ TOATE driverele hardware (>60% din codul total)
│ ├── char/ Character devices
│ ├── block/ Block devices
│ ├── net/ Network drivers
│ ├── gpu/ GPU (DRM/KMS)
│ ├── usb/ USB stack
│ ├── pci/ PCI/PCIe
│ ├── i2c/ I²C bus și drivere
│ ├── spi/ SPI bus și drivere
│ ├── gpio/ GPIO subsystem
│ ├── input/ Tastatură, mouse, touchscreen
│ ├── tty/ Terminal/serial
│ └── ...
├── fs/ Sisteme de fișiere (ext4, btrfs, xfs, tmpfs, proc, sysfs)
├── include/ Headere globale
│ ├── linux/ Headere core kernel
│ ├── uapi/ Headere exportate către userspace
│ └── asm-generic/ Headere generice arhitecturală
├── init/ Codul de inițializare (main.c → start_kernel)
├── ipc/ Inter-Process Communication (semafoare, cozi, shm)
├── kernel/ Componente core: scheduling, signals, time, locking
│ ├── sched/ Scheduler-ul CFS și clasele de scheduling
│ ├── locking/ Mutexuri, spinlocks, RCU
│ ├── irq/ Infrastructura de întreruperi
│ └── time/ Timere, hrtimers, timekeeping
├── lib/ Funcții utilitare (string, sort, bitmap, rbtree)
├── mm/ Memory Management (fizic, virtual, slab, page cache)
├── net/ Stiva de rețea (TCP/IP, socket, netfilter)
│ ├── core/
│ ├── ipv4/
│ ├── ipv6/
│ └── netfilter/
├── scripts/ Scripturi de build, Kconfig
├── security/ LSM (SELinux, AppArmor, SMACK)
├── sound/ ALSA (Advanced Linux Sound Architecture)
├── tools/ Instrumente userspace (perf, bpf)
├── virt/ Virtualizare (KVM)
├── Kconfig Configurare kernel
├── Makefile Build system
└── MAINTAINERS Cine menține ce subsistem
1.4 Compilarea kernel-ului¶
# 1. Descărcare sursă
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
# 2. Configurare
make menuconfig # interfață ncurses interactivă
# sau:
make defconfig # configurare implicită pentru arhitectura curentă
make x86_64_defconfig # configurare specifică
# sau pornire de la config-ul distribuției:
cp /boot/config-$(uname -r) .config
make olddefconfig
# 3. Compilare
make -j$(nproc) # compilare paralelă pe toate nucleele
# 4. Instalare
sudo make modules_install # instalează modulele în /lib/modules/
sudo make install # instalează vmlinuz, System.map, initramfs
# 5. Cross-compilare pentru ARM
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
1.5 Configurarea kernel-ului (Kconfig)¶
Sistemul Kconfig permite selectarea componentelor kernel-ului:
# Exemplu din drivers/char/Kconfig
config DEVMEM
bool "/dev/mem virtual device support"
default y
help
Say Y here if you want to support the /dev/mem device.
The /dev/mem device is used to access areas of physical
memory.
config TTY
bool "Enable TTY" if EXPERT
default y
help
Allows you to remove TTY support which can save space.
# Opțiunile pot fi:
# bool → Y sau N (compilat în kernel sau exclus)
# tristate → Y, M sau N (Y=builtin, M=modul, N=exclus)
# int → valoare numerică
# string → valoare text
# depends on → dependințe
# select → activează automat alte opțiuni
2. Spațiul kernel vs. spațiul utilizator¶
2.1 Niveluri de privilegiu hardware¶
Procesorul oferă niveluri de protecție care restricționează ce cod poate face:
x86 — Ring-uri de protecție:
Ring 0: Kernel (acces complet: toate instrucțiunile, toată memoria, toate porturile I/O)
Ring 1: (nefolosit pe Linux)
Ring 2: (nefolosit pe Linux)
Ring 3: Userspace (restricționat: nu poate accesa hardware direct,
nu poate modifica page tables, nu poate dezactiva întreruperi)
Tranziția Ring 3 → Ring 0: prin syscall, excepție sau întrerupere
Tranziția Ring 0 → Ring 3: prin sysret/iret
ARM — Exception Levels (ARMv8-A):
EL0: Application (userspace) — neprivilegiat
EL1: OS Kernel — privilegiat, gestionează MMU, întreruperi
EL2: Hypervisor — virtualizare (KVM pe Linux)
EL3: Secure Monitor — ARM TrustZone, Secure Boot
Tranziția EL0 → EL1: prin SVC (Supervisor Call), excepție sau întrerupere
Tranziția EL1 → EL0: prin ERET (Exception Return)
2.2 Separarea spațiilor de adresare¶
Kernel-ul și aplicațiile au spații de adresare virtuale separate, asigurate de MMU:
x86-64 cu adrese pe 48 biți (canonice):
0x0000000000000000 ┌───────────────────────┐
│ │
│ Userspace (128 TB) │ Accesibil din Ring 3
│ │ Fiecare proces are
│ Text, Data, Heap │ propria lui mapare
│ Shared Libraries │
│ Stack │
│ mmap() regions │
0x00007FFFFFFFFFFF ├───────────────────────┤
│ (non-canonical │ Adrese invalide
│ hole) │ Protecție arhitecturală
0xFFFF800000000000 ├───────────────────────┤
│ │
│ Kernel Space (128TB)│ Accesibil doar Ring 0
│ │ IDENTIC pentru toate
│ Direct mapping │ procesele
│ vmalloc area │
│ Kernel text/data │
│ Module space │
│ Fixmap │
0xFFFFFFFFFFFFFFFF └───────────────────────┘
ARM64 cu adrese pe 48 biți (TTBR0 / TTBR1):
Userspace: 0x0000_0000_0000_0000 – 0x0000_FFFF_FFFF_FFFF (TTBR0_EL1)
Kernel: 0xFFFF_0000_0000_0000 – 0xFFFF_FFFF_FFFF_FFFF (TTBR1_EL1)
2.3 Transferul de date între kernel și userspace¶
Kernel-ul nu poate accesa direct pointerii userspace (ar fi o vulnerabilitate de securitate). Se folosesc funcții dedicate care verifică validitatea adreselor:
#include <linux/uaccess.h>
/* Copiază date DIN userspace ÎN kernel */
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
/* Copiază date DIN kernel ÎN userspace */
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
/* Citește/scrie o singură valoare */
int get_user(type val, const type __user *ptr);
int put_user(type val, type __user *ptr);
/* Exemplu practic într-un ioctl handler: */
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct my_data data;
switch (cmd) {
case MY_IOC_SET:
/* Citim structura din userspace */
if (copy_from_user(&data, (struct my_data __user *)arg, sizeof(data)))
return -EFAULT; /* -EFAULT = adresă invalidă */
/* ... procesăm data ... */
break;
case MY_IOC_GET:
/* ... populăm data ... */
/* Scriem structura în userspace */
if (copy_to_user((struct my_data __user *)arg, &data, sizeof(data)))
return -EFAULT;
break;
}
return 0;
}
Adnotarea __user este un marker semantic verificat de instrumentul Sparse (make C=1). Nu are efect la compilare, dar ajută la detectarea acceselor greșite.
2.4 Contextul de execuție în kernel¶
Codul kernel poate rula în două contexte fundamentale:
| Proprietate | Context de proces | Context de întrerupere |
|---|---|---|
| Cine l-a declanșat | Syscall, excepție, fault | Hardware IRQ, softirq |
current valid |
Da (procesul curent) | Nu (fără proces asociat) |
| Poate dormi (sleep) | Da (poate apela schedule()) | NU — niciodată! |
| Poate accesa user | Da (copy_from/to_user) | Nu |
| Preemptibil | Da (cu CONFIG_PREEMPT) | Nu (în hardirq) |
| Stivă | Stiva kernel a procesului | Stivă separată per CPU |
Regulă de aur: în context de întrerupere nu poți dormi. Aceasta afectează ce funcții poți apela (nu poți aloca memorie cu GFP_KERNEL, nu poți folosi mutexuri, nu poți citi de la userspace).
3. Pornirea kernel-ului și inițializarea sistemului¶
3.1 Secvența de boot Linux (x86-64)¶
BIOS/UEFI
│
▼
Bootloader (GRUB2, systemd-boot)
│ Încarcă vmlinuz (kernel comprimat) + initramfs la o adresă în RAM
│ Transmite parametri boot (kernel command line, adresa DTB pe ARM)
▼
Decompresie kernel (arch/x86/boot/compressed/head_64.S)
│ Setează page tables inițiale, trece în Long Mode (64-bit)
▼
start_kernel() — init/main.c (punctul de intrare principal C)
│
├─ setup_arch() Detectare hardware, parsare e820 memory map
├─ mm_init() Inițializare alocator de pagini (buddy system)
├─ sched_init() Inițializare scheduler
├─ init_IRQ() Configurare controllere de întreruperi
├─ time_init() Inițializare sursă de ceas
├─ console_init() Prima consolă (earlycon)
├─ vfs_caches_init() Inițializare VFS, mount rootfs
├─ fork_init() Configurare limită maximă de procese
├─ signals_init() Inițializare semnal
├─ ...
│
└─ arch_call_rest_init()
│
└─ rest_init()
│
├─ kernel_thread(kernel_init, ...) → PID 1
│ │
│ ├─ Încarcă initramfs
│ ├─ Montează root filesystem real
│ ├─ free_initmem() (eliberare memorie init)
│ └─ run_init_process("/sbin/init")
│ → systemd / SysVinit / OpenRC
│
└─ kernel_thread(kthreadd, ...) → PID 2
│
└─ Gestiunea tuturor kernel thread-urilor
3.2 start_kernel() — anatomie¶
/* init/main.c — simplificat */
void __init start_kernel(void)
{
/* Configurare stivă, canary pentru stack protection */
set_task_stack_end_magic(&init_task);
/* Inițializare lock-uri de bază, printk timpuriu */
lockdep_init();
boot_init_stack_canary();
/* Parsare linie de comandă kernel
* ex: "console=ttyS0,115200 root=/dev/sda1 quiet" */
setup_command_line(command_line);
/* Inițializare specifică arhitecturii */
setup_arch(&command_line);
/* Inițializare alocator slab (kmalloc) */
mm_init();
/* Inițializare scheduler */
sched_init();
/* Dezactivare preemțiune până la inițializarea completă */
preempt_disable();
/* Inițializare IRQ, timere, console */
init_IRQ();
tick_init();
init_timers();
softirq_init();
time_init();
console_init();
/* Inițializare subsistem module */
/* Inițializare rețea, VFS, etc. */
vfs_caches_init();
signals_init();
/* Apelează funcțiile marcate cu __init (initcalls) */
/* Ordinea: early → pure → core → postcore → arch →
* subsys → fs → device → late */
/* Creează kernel_init (PID 1) și kthreadd (PID 2) */
arch_call_rest_init();
/* Nu se ajunge niciodată aici */
}
3.3 Nivelurile de inițializare (initcalls)¶
/* Funcțiile de inițializare sunt categorizate pe nivele de prioritate.
* Linkerul le grupează în secțiuni speciale.
* Kernel-ul le apelează în ordine la boot. */
/* Macro-urile de declarare: */
pure_initcall(fn) /* Nivel 0: primele, fără dependențe */
core_initcall(fn) /* Nivel 1: core subsystem */
postcore_initcall(fn) /* Nivel 2: după core */
arch_initcall(fn) /* Nivel 3: inițializări de arhitectură */
subsys_initcall(fn) /* Nivel 4: subsisteme (PCI, USB, I2C bus) */
fs_initcall(fn) /* Nivel 5: sisteme de fișiere */
device_initcall(fn) /* Nivel 6: drivere de dispozitiv (= module_init) */
late_initcall(fn) /* Nivel 7: ultimele */
/* Exemplu: */
static int __init my_subsys_init(void)
{
pr_info("My subsystem initialized\n");
return 0;
}
subsys_initcall(my_subsys_init);
/* Funcțiile __init sunt eliberate din memorie după boot (free_initmem),
* economisind RAM — marcat prin secțiunea specială .init.text */
3.4 Boot pe ARM64 cu Device Tree¶
Bootloader (U-Boot):
│ Încarcă: Image (kernel), dtb (device tree blob), initramfs
│ Plasează DTB-ul la o adresă, transmite adresa prin X0
▼
Kernel ARM64 entry point (arch/arm64/kernel/head.S):
│ X0 = adresa DTB
│ Verificare magic number DTB
│ Setare page tables, activare MMU
│ Salt la start_kernel()
▼
start_kernel() → setup_arch():
│ unflatten_device_tree() — parsare DTB în structuri of_*
│ Detectare memorie din nodul /memory
│ Detectare CPU-uri din noduri /cpus/cpu@*
│ Identificare periferice prin proprietatea "compatible"
▼
Fiecare driver se potrivește pe baza "compatible":
static const struct of_device_id my_match[] = {
{ .compatible = "vendor,my-device" },
{ }
};
MODULE_DEVICE_TABLE(of, my_match);
4. Gestiunea proceselor și planificarea (Scheduling)¶
4.1 Procesul în Linux — task_struct¶
Fiecare proces (și fiecare thread) este reprezentat intern de o structură task_struct (definită în include/linux/sched.h). Este una dintre cele mai mari structuri din kernel (~8 KB).
/* Câmpuri esențiale ale task_struct (puternic simplificat): */
struct task_struct {
/* === Stare === */
unsigned int __state; /* TASK_RUNNING, TASK_INTERRUPTIBLE, etc. */
int exit_state;
int exit_code;
/* === Identificare === */
pid_t pid; /* Process ID (unic per thread) */
pid_t tgid; /* Thread Group ID (= PID-ul vizibil din userspace) */
struct task_struct *real_parent; /* Procesul părinte real */
struct task_struct *parent; /* Procesul părinte (poate diferi cu ptrace) */
struct list_head children; /* Lista copiilor */
struct list_head sibling; /* Lista fraților */
struct task_struct *group_leader; /* Liderul thread group-ului */
/* === Scheduling === */
int prio; /* Prioritatea dinamică (0-139) */
int static_prio; /* Prioritatea statică (setată de nice) */
int normal_prio;
unsigned int rt_priority; /* Prioritate real-time (0-99) */
const struct sched_class *sched_class; /* Clasa de scheduling (CFS, RT, DL) */
struct sched_entity se; /* Entitate CFS (vruntime, etc.) */
struct sched_rt_entity rt; /* Entitate real-time */
struct sched_dl_entity dl; /* Entitate deadline */
unsigned int policy; /* SCHED_NORMAL, SCHED_FIFO, SCHED_RR, SCHED_DEADLINE */
cpumask_t cpus_mask; /* Pe ce CPU-uri poate rula */
/* === Memorie === */
struct mm_struct *mm; /* Spațiul de adresare (NULL pt kernel threads) */
struct mm_struct *active_mm;
/* === Fișiere === */
struct files_struct *files; /* Tabela de file descriptori */
struct fs_struct *fs; /* Root dir, cwd */
/* === Credențiale și securitate === */
const struct cred *cred; /* UID, GID, capabilities */
struct nsproxy *nsproxy; /* Namespace-uri (PID, mount, net, ...) */
/* === Semnale === */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked; /* Semnale blocate */
/* === Stiva kernel === */
void *stack; /* Baza stivei kernel (tipic 16KB pe x86-64) */
/* === Timpi === */
u64 utime; /* Timp în userspace (ns) */
u64 stime; /* Timp în kernel (ns) */
/* ... sute de alte câmpuri ... */
};
4.2 Stările unui proces¶
fork()/clone()
│
▼
┌───────────────┐
│ TASK_RUNNING │ ← în coada de rulare (runqueue)
│ (Ready/Running│ CPU-ul execută sau gata de execuție
└───┬───────┬───┘
│ │
schedule()│ │ event (I/O, sleep, mutex)
│ │
│ ▼
│ ┌──────────────────┐
│ │TASK_INTERRUPTIBLE │ ← doarme, poate fi trezit
│ │ (sau │ de semnal SAU de eveniment
│ │ TASK_UNINTERRUPT.)│ ← doarme, DOAR de eveniment
│ └────────┬─────────┘
│ │ wake_up() / semnal
│ │
◄───────────┘
│
│ do_exit()
▼
┌──────────────┐
│ EXIT_ZOMBIE │ ← proces terminat, dar părintele
│ │ nu a apelat wait() încă
└──────┬───────┘
│ wait() / waitpid() de către părinte
▼
┌──────────────┐
│ EXIT_DEAD │ ← structurile sunt eliberate
└──────────────┘
Stări suplimentare:
TASK_STOPPED — oprit prin SIGSTOP sau ptrace
TASK_TRACED — oprit de debugger (ptrace)
4.3 Crearea proceselor — fork(), vfork(), clone()¶
/* În kernel, toate se rezumă la _do_fork() / kernel_clone():
fork() → clone(SIGCHLD, 0)
vfork() → clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0)
pthread_create() → clone(CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_THREAD | SIGCHLD, stack)
*/
/* Flag-uri clone() esențiale: */
#define CLONE_VM 0x00000100 /* Partajează spațiul de adresare (thread) */
#define CLONE_FS 0x00000200 /* Partajează informații filesystem (cwd, root) */
#define CLONE_FILES 0x00000400 /* Partajează tabela de descriptori */
#define CLONE_SIGHAND 0x00000800 /* Partajează signal handlers */
#define CLONE_THREAD 0x00010000 /* Același thread group (PID partajat) */
#define CLONE_NEWNS 0x00020000 /* Nou mount namespace */
#define CLONE_NEWPID 0x20000000 /* Nou PID namespace (containere) */
#define CLONE_NEWNET 0x40000000 /* Nou network namespace */
Copy-on-Write (COW):
La fork(), kernel-ul nu copiază paginile de memorie ale procesului părinte. Ambele procese partajează aceleași pagini fizice (marcate read-only). Când unul scrie, apare un page fault, iar kernel-ul face atunci copia efectivă doar pentru pagina modificată. Aceasta face fork() extrem de rapid.
Înainte de fork():
Proces P: [pagina A] [pagina B] [pagina C]
│ │ │
▼ ▼ ▼
Fizic: [frame 1] [frame 2] [frame 3]
După fork() (COW):
Proces P: [pagina A] [pagina B] [pagina C] ← marcate R/O
│ │ │
▼ ▼ ▼
Fizic: [frame 1] [frame 2] [frame 3] ← partajate
▲ ▲ ▲
│ │ │
Proces C: [pagina A] [pagina B] [pagina C] ← marcate R/O
Proces C scrie pagina B → page fault:
Proces P: [pagina A] [pagina B] [pagina C]
│ │ │
▼ ▼ ▼
Fizic: [frame 1] [frame 2] [frame 3]
▲ ▲
│ │
Proces C: [pagina A] [pagina B] [pagina C]
│
▼
[frame 4] ← copie nouă a paginii B
4.4 Macro-ul current — accesarea procesului curent¶
/* 'current' este un macro care returnează pointer la task_struct
* a procesului care rulează pe CPU-ul curent.
*
* Pe x86-64: task_struct este stocat în registrul per-cpu GS.
* Pe ARM64: stocat în registrul dedicat SP_EL0 sau TPIDR_EL1.
*
* Acces extrem de rapid (o instrucțiune). */
struct task_struct *p = current;
pr_info("PID: %d, Comm: %s\n", current->pid, current->comm);
/* Exemplu: verificarea dacă procesul curent are drepturi root */
if (current_uid().val == 0) {
/* Procesul rulează ca root */
}
/* Metoda modernă: capabilities */
if (capable(CAP_SYS_ADMIN)) {
/* Procesul are capacitatea SYS_ADMIN */
}
4.5 Scheduler-ul CFS (Completely Fair Scheduler)¶
CFS (din 2007, kernel 2.6.23) este scheduler-ul implicit pentru procesele normale (SCHED_NORMAL/SCHED_OTHER). Filosofia: fiecare proces ar trebui să primească o fracțiune egală din timpul CPU.
Conceptul vruntime:
Fiecare proces acumulează vruntime (virtual runtime) pe măsură ce rulează. CFS alege mereu procesul cu cel mai mic vruntime din arbore. Procesele cu prioritate mai mare (nice mai mic) acumulează vruntime mai lent.
/* Relația nice → weight: */
/* nice -20 (cel mai prioritar) → weight 88761 */
/* nice 0 (default) → weight 1024 */
/* nice +19 (cel mai neprioritар) → weight 15 */
/* vruntime crește proporțional cu timpul real, invers cu weight-ul:
* delta_vruntime = delta_exec * (NICE_0_LOAD / weight)
*
* Exemplu: nice=0 rulează 10ms → vruntime += 10ms
* nice=-5 rulează 10ms → vruntime += ~3ms (se „mișcă" mai lent)
* nice=+5 rulează 10ms → vruntime += ~30ms (se „mișcă" mai repede)
*/
Structura de date — Red-Black Tree:
CFS folosește un arbore roșu-negru (RB-tree) pentru a menține procesele ordonate după vruntime. Accesul la procesul cu cel mai mic vruntime: O(1) (elementul cel mai din stânga este memorat în cache). Inserare/ștergere: O(log n).
┌─────────────┐
│ vruntime=50 │ (rădăcina RB-tree)
└──┬──────┬───┘
│ │
┌────────┴─┐ ┌─┴────────┐
│vrt=30 │ │ vrt=70 │
└──┬───┬───┘ └──┬───┬───┘
│ │ │ │
┌────┴┐ ┌┴────┐ ┌──┴─┐ ┌┴────┐
│vr=20│ │vr=40│ │vr=60│ │vr=90│
└─────┘ └─────┘ └────┘ └─────┘
↑
Următorul de rulat
(cel mai mic vruntime)
4.6 Clasele de scheduling¶
/* Linux suportă mai multe politici de scheduling, organizate
* ierarhic pe clase cu prioritate descrescătoare: */
/* 1. SCHED_DEADLINE (clasa dl_sched_class) — cea mai prioritară */
/* - Parametri: runtime, deadline, period */
/* - Garantează că task-ul primește 'runtime' μs în fiecare 'period' */
/* - Algoritm: Earliest Deadline First (EDF) + CBS */
/* 2. SCHED_FIFO, SCHED_RR (clasa rt_sched_class) */
/* - Prioritate fixă (0-99), mai mare = mai prioritar */
/* - FIFO: rulează până se blochează sau este preemptat de prio mai mare */
/* - RR: ca FIFO, dar cu timeslice (implicit 100ms) */
/* 3. SCHED_NORMAL/SCHED_OTHER, SCHED_BATCH, SCHED_IDLE (clasa fair_sched_class) */
/* - CFS cu nice (-20 la +19) */
/* - SCHED_BATCH: nu preemptează frecvent, pentru calcule intensive */
/* - SCHED_IDLE: prioritate minimă (background) */
/* Ierarhie de decizie: */
/* schedule() → pick_next_task():
* 1. Verifică dacă există task DEADLINE gata → rulează-l
* 2. Altfel, verifică real-time (FIFO/RR) → rulează cel mai prioritar
* 3. Altfel, CFS → rulează cel cu vruntime minim
* 4. Altfel, idle task
*/
/* Setarea politicii din userspace: */
/* chrt -f 50 ./program # SCHED_FIFO cu prioritate 50 */
/* chrt -r 10 ./program # SCHED_RR cu prioritate 10 */
/* nice -n -5 ./program # CFS cu nice -5 */
4.7 Context switch¶
La fiecare comutare între procese, kernel-ul trebuie să salveze starea procesorului și să restaureze starea altui proces:
/* kernel/sched/core.c — simplificat */
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next)
{
/* 1. Comutare spațiu de adresare (page tables) */
if (!next->mm) {
/* Kernel thread: reutilizează mm-ul procesului anterior */
next->active_mm = prev->active_mm;
} else {
/* Proces userspace: schimbă CR3 (x86) sau TTBR0 (ARM) */
switch_mm(prev->active_mm, next->mm, next);
}
/* 2. Comutare starea registrelor CPU (context hardware) */
switch_to(prev, next, prev);
/* switch_to() este scris în assembly, specifică arhitecturii:
* - Salvează registrele callee-saved pe stiva kernel a lui prev
* - Comută stack pointer-ul la stiva kernel a lui next
* - Restaurează registrele callee-saved de pe stiva lui next
* - Continuă execuția de unde next a fost suspendat */
return rq;
}
; x86-64: switch_to (arch/x86/kernel/process_64.c / entry)
; Simplificat — esența comutării:
; Salvare registre pe stiva veche
push rbp
push rbx
push r12
push r13
push r14
push r15
; Comutare stack pointer
mov [prev_rsp], rsp ; salvează RSP al procesului vechi
mov rsp, [next_rsp] ; încarcă RSP al procesului nou
; Restaurare registre de pe stiva nouă
pop r15
pop r14
pop r13
pop r12
pop rbx
pop rbp
; RET → revine în codul procesului nou
; (adresa de retur era pe stiva lui next)
ret
; ARM64: cpu_switch_to (arch/arm64/kernel/entry.S)
; Simplificat:
mov x10, #THREAD_CPU_CONTEXT
add x8, x0, x10 /* x8 = &prev->thread.cpu_context */
/* Salvare registre callee-saved ale procesului vechi */
stp x19, x20, [x8, #0]
stp x21, x22, [x8, #16]
stp x23, x24, [x8, #32]
stp x25, x26, [x8, #48]
stp x27, x28, [x8, #64]
stp x29, lr, [x8, #80]
mov x9, sp
str x9, [x8, #96] /* Salvează SP */
add x8, x1, x10 /* x8 = &next->thread.cpu_context */
/* Restaurare registre ale procesului nou */
ldp x19, x20, [x8, #0]
ldp x21, x22, [x8, #16]
ldp x23, x24, [x8, #32]
ldp x25, x26, [x8, #48]
ldp x27, x28, [x8, #64]
ldp x29, lr, [x8, #80]
ldr x9, [x8, #96]
mov sp, x9 /* Restaurează SP */
ret /* Salt la unde era next */
4.8 Per-CPU runqueues și load balancing¶
Fiecare CPU are propria sa coadă de rulare (struct rq). Scheduler-ul operează local pe fiecare CPU, iar un mecanism de load balancing redistribuie periodic procesele între CPU-uri:
CPU 0 runqueue: CPU 1 runqueue: CPU 2 runqueue:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ CFS rb-tree: │ │ CFS rb-tree: │ │ CFS rb-tree: │
│ 5 procese │ │ 2 procese │ │ 8 procese │
│ load=5120 │ │ load=2048 │ │ load=8192 │
└──────────────┘ └──────────────┘ └──────────────┘
▲ │
│ Load Balancer │
└────── migrează ────────┘
3 procese
5. Apeluri de sistem (System Calls)¶
5.1 Mecanismul complet al unui syscall¶
Aplicație userspace:
printf("hello") → write(1, "hello", 5)
│
▼
glibc wrapper (libc):
Pune argumentele în registre conform ABI:
x86-64: RAX=nr_syscall, RDI=fd, RSI=buf, RDX=count
ARM64: X8=nr_syscall, X0=fd, X1=buf, X2=count
│
▼
Instrucțiunea syscall/SVC:
x86-64: instrucțiunea 'syscall'
ARM64: instrucțiunea 'SVC #0'
│
▼
Hardware:
- Salvează RIP → RCX, RFLAGS → R11 (x86)
- Salvează PC, PSTATE (ARM)
- Comută la Ring 0 / EL1
- Salt la entry point kernel
│
▼
Entry point kernel (assembly):
x86-64: entry_SYSCALL_64 (arch/x86/entry/entry_64.S)
ARM64: el0_sync (arch/arm64/kernel/entry.S)
│
Salvare registre pe stiva kernel (struct pt_regs)
Verificare nr syscall valid
│
▼
Tabela de syscalls:
sys_call_table[__NR_write] → __x64_sys_write → ksys_write()
│
▼
Implementare kernel (fs/read_write.c):
ksys_write() → vfs_write() → file->f_op->write()
│
▼
Rezultat:
Valoare de retur în RAX (x86) / X0 (ARM)
Dacă eroare: returnează -errno (negativ)
│
▼
Returnare în userspace:
x86-64: instrucțiunea 'sysret'
ARM64: instrucțiunea 'ERET'
│
▼
glibc:
Verifică dacă RAX < 0
Dacă da: errno = -RAX, returnează -1
Dacă nu: returnează RAX
5.2 Definirea unui syscall¶
/* Definire syscall în kernel (exemplu simplificat): */
/* include/linux/syscalls.h — declarație */
asmlinkage long sys_write(unsigned int fd, const char __user *buf,
size_t count);
/* fs/read_write.c — implementare */
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
{
struct fd f = fdget_pos(fd);
ssize_t ret;
if (!f.file)
return -EBADF;
ret = vfs_write(f.file, buf, count, &f.file->f_pos);
fdput_pos(f);
return ret;
}
/* SYSCALL_DEFINE3 este un macro care:
* 1. Generează funcția __x64_sys_write (entry point din tabelă)
* 2. Extrage argumentele din struct pt_regs
* 3. Apelează funcția de implementare reală */
/* Tabela de syscalls (arch/x86/entry/syscalls/syscall_64.tbl): */
/* nr abi name entry point */
/* 0 common read sys_read */
/* 1 common write sys_write */
/* 2 common open sys_open */
/* ... */
5.3 VDSO — syscalls fără tranziție în kernel¶
Unele syscalls frecvente dar care nu necesită acces privilegiat (citirea ceasului, obținerea PID-ului) sunt accelerate prin VDSO (Virtual Dynamic Shared Object):
/* VDSO-ul este o pagină specială mapată automat în address space-ul
* fiecărui proces de către kernel. Conține cod kernel care rulează
* în userspace (Ring 3) fără a face tranziția Ring 3→0. */
/* Exemplu: gettimeofday() */
/* Fără VDSO: ~100-200 ns (syscall overhead) */
/* Cu VDSO: ~10-20 ns (citire directă din pagina partajată) */
/* Kernel-ul actualizează periodic datele în pagina VDSO.
* Funcții VDSO tipice: clock_gettime, gettimeofday, getcpu, time */
/* Vizualizare VDSO: */
/* $ cat /proc/self/maps | grep vdso
* 7fff9a5fe000-7fff9a600000 r-xp ... [vdso] */
6. Gestiunea memoriei¶
6.1 Alocatorul de pagini fizice — Buddy System¶
Kernel-ul gestionează memoria fizică în unități de pagini (4 KB pe x86/ARM). Alocatorul buddy organizează paginile libere în liste de blocuri de puteri ale lui 2:
Ordinul: 0 1 2 3 4 ... 10
Dimensiune: 4KB 8KB 16KB 32KB 64KB 4MB
Free lists:
Order 0: [page] [page] [page] [page] ...
Order 1: [2 pages] [2 pages] ...
Order 2: [4 pages] [4 pages] ...
...
Order 10: [1024 pages = 4MB] [1024 pages] ...
Alocare: se caută cel mai mic bloc disponibil >= dimensiunea cerută. Dacă nu există, se împarte un bloc mai mare în doi „prieteni” (buddies).
Eliberare: se verifică dacă „prietenul” este liber. Dacă da, se combină (coalescing) și se promovează la ordinul superior. Procedeul se repetă recursiv.
/* API-ul de alocare de pagini: */
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
void free_pages(unsigned long addr, unsigned int order);
/* Exemplu: alocă 4 pagini contigue (16 KB) */
struct page *pg = alloc_pages(GFP_KERNEL, 2); /* order=2 → 2²=4 pagini */
void *vaddr = page_address(pg);
/* Flag-uri GFP (Get Free Pages) importante: */
#define GFP_KERNEL /* Normal: poate dormi, poate face I/O */
#define GFP_ATOMIC /* Nu poate dormi! Pentru context IRQ */
#define GFP_DMA /* Memorie accesibilă DMA legacy (sub 16MB) */
#define GFP_HIGHUSER /* Pentru pagini userspace */
/* /proc/buddyinfo — vizualizare starea alocatorului: */
/* Node 0, zone DMA32 12 8 4 3 2 1 0 0 0 1 2 */
/* Node 0, zone Normal 234 85 43 21 8 3 1 0 0 0 1 */
/* ord 0 1 2 3 4 5 6 7 8 9 10 */
6.2 Zonele de memorie¶
Memoria fizică este împărțită în zone bazate pe limitări hardware:
x86-64 zone:
┌──────────────────┐ 0x0
│ ZONE_DMA │ 0–16 MB (ISA DMA legacy)
├──────────────────┤ 16 MB
│ ZONE_DMA32 │ 16 MB–4 GB (dispozitive DMA 32-bit)
├──────────────────┤ 4 GB
│ ZONE_NORMAL │ 4 GB–max (memoria principală)
└──────────────────┘
ARM64 zone:
┌──────────────────┐
│ ZONE_DMA │ Depinde de DMA mask (tipic 0–4GB)
├──────────────────┤
│ ZONE_DMA32 │ 0–4 GB
├──────────────────┤
│ ZONE_NORMAL │ Restul
└──────────────────┘
6.3 Alocat de obiecte — SLAB/SLUB/SLOB¶
Alocarea individuală de pagini (4KB minim) este risipitoare pentru obiecte mici. Alocatoarele slab creează cache-uri de obiecte de dimensiune fixă:
/* Creare cache slab pentru o structură frecvent alocată: */
static struct kmem_cache *my_cache;
/* La inițializare: */
my_cache = kmem_cache_create("my_objects", /* nume */
sizeof(struct my_obj), /* dimensiune */
0, /* aliniere */
SLAB_HWCACHE_ALIGN,/* flags */
NULL); /* constructor opțional */
/* Alocare obiect: */
struct my_obj *obj = kmem_cache_alloc(my_cache, GFP_KERNEL);
/* Eliberare: */
kmem_cache_free(my_cache, obj);
/* Distrugere cache: */
kmem_cache_destroy(my_cache);
/* kmalloc() folosește intern un set de slab cache-uri predefinite:
* kmalloc-8, kmalloc-16, kmalloc-32, ..., kmalloc-8192
* kmalloc(50, GFP_KERNEL) → alocă din cache-ul kmalloc-64 */
SLUB (implicit pe Linux modern): simplu, performant, bun debugging.
SLAB (clasic): complex, metadata per cache.
SLOB (pentru sisteme embedded cu RAM foarte puțin).
6.4 API-uri de alocare memorie în kernel¶
/* === Alocare generală (heap kernel) === */
void *kmalloc(size_t size, gfp_t flags); /* Memorie fizic contiguă */
void kfree(const void *ptr);
void *kzalloc(size_t size, gfp_t flags); /* kmalloc + zero-izare */
void *kcalloc(size_t n, size_t size, gfp_t flags); /* array, cu verificare overflow */
void *krealloc(const void *p, size_t new_size, gfp_t flags);
/* === Alocare virtuală contiguă (fizic poate fi fragmentat) === */
void *vmalloc(unsigned long size); /* Pagini mapate în vmalloc area */
void vfree(const void *addr); /* Mai lent, dar fără fragmentare */
/* Folosit pentru buffere mari (>128KB) unde contiguitatea fizică nu e necesară */
/* === Alocare DMA-safe (contiguă fizic, cu adresă fizică) === */
void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp);
void dma_free_coherent(struct device *dev, size_t size,
void *vaddr, dma_addr_t dma_handle);
/* === Per-CPU data (evită contention pe cache-uri partajate) === */
DEFINE_PER_CPU(int, my_counter);
per_cpu(my_counter, cpu_id)++;
this_cpu_inc(my_counter);
6.5 Memory mapping — spațiul de adresare al procesului¶
/* Structura mm_struct descrie întregul spațiu de adresare: */
struct mm_struct {
struct maple_tree mm_mt; /* Arbore de VMAs (înlocuiește rb-tree) */
unsigned long mmap_base; /* Baza zonei mmap */
unsigned long start_code, end_code; /* .text */
unsigned long start_data, end_data; /* .data */
unsigned long start_brk, brk; /* heap */
unsigned long start_stack; /* stivă */
unsigned long total_vm; /* Nr. total pagini mapate */
unsigned long locked_vm; /* Pagini blocate (mlock) */
pgd_t *pgd; /* Tabela de pagini (nivel 1) */
atomic_t mm_users; /* Nr. utilizatori (procese/thread-uri) */
atomic_t mm_count; /* Nr. referințe (include kernel) */
};
/* Fiecare regiune de memorie mapată = un VMA: */
struct vm_area_struct {
unsigned long vm_start; /* Adresă virtuală start */
unsigned long vm_end; /* Adresă virtuală end (exclusivă) */
unsigned long vm_flags; /* Permisiuni: VM_READ, VM_WRITE, VM_EXEC */
struct file *vm_file; /* Fișierul mapat (sau NULL pt anon) */
pgoff_t vm_pgoff; /* Offset în fișier */
const struct vm_operations_struct *vm_ops; /* Operații (fault handler, etc.) */
};
/* Vizualizare: cat /proc/<pid>/maps */
/* 00400000-00452000 r-xp 00000000 08:01 131078 /usr/bin/bash (.text) */
/* 00651000-00652000 r--p 00051000 08:01 131078 /usr/bin/bash (.rodata) */
/* 00652000-0065b000 rw-p 00052000 08:01 131078 /usr/bin/bash (.data) */
/* 01a5f000-01ae8000 rw-p 00000000 00:00 0 [heap] */
/* 7f1234500000-... r-xp 00000000 08:01 393222 /lib/libc.so.6 */
/* 7ffd3a6e0000-7ffd3a701000 rw-p 00000000 00:00 0 [stack] */
/* 7ffd3a7fe000-7ffd3a800000 r-xp 00000000 00:00 0 [vdso] */
6.6 Page fault handler¶
Când CPU-ul accesează o adresă virtuală fără mapare fizică validă, apare un page fault (excepție). Kernel-ul decide ce trebuie făcut:
/* Fluxul simplificat al page fault handler
* (arch/x86/mm/fault.c → handle_mm_fault → handle_pte_fault): */
/*
* 1. CPU generează excepție #PF (#14 pe x86)
* 2. Kernel primește adresa faultului (CR2 pe x86, FAR_EL1 pe ARM)
* 3. Verifică dacă adresa aparține unui VMA valid
* - NU → SIGSEGV (Segmentation Fault) → kill proces
* - DA → continuă:
*
* 4. Verifică permisiunile VMA vs. tipul accesului
* - Scriere în zonă read-only non-COW → SIGSEGV
*
* 5. Identifică tipul fault-ului:
* a) Minor fault (pagina e în memorie, doar PTE lipsește):
* - COW fault: copiază pagina, actualizează PTE → continuă
* - Zero page: alocă pagină zero-izată → continuă
* b) Major fault (pagina nu e în memorie):
* - Citire din fișier mapat (mmap): citește de pe disk → continuă
* - Pagină swap-ată: citește din swap → continuă
* - Demand paging: alocă pagină nouă → continuă
*
* 6. Actualizează PTE, flush TLB
* 7. Reia instrucțiunea care a cauzat fault-ul
*/
/* Contorizare: /proc/<pid>/stat → câmpurile minflt, majflt */
7. Sistemul de fișiere virtual (VFS)¶
7.1 Arhitectura VFS¶
VFS este stratul de abstractizare care permite Linux-ului să suporte zeci de sisteme de fișiere diferite printr-o interfață unificată:
Aplicație: open(), read(), write(), close(), stat(), mmap()
│
▼
┌────────────────┐
│ System Call │
│ Interface │
└────────┬────────┘
│
┌────────▼────────┐
│ VFS │ Interfață uniformă
│ │ Obiectele: superblock, inode, dentry, file
└──┬─────┬────┬───┘
│ │ │
┌─────▼─┐ ┌─▼──┐ ┌▼──────┐
│ ext4 │ │xfs │ │btrfs │ Sisteme de fișiere pe disc
└───┬───┘ └─┬──┘ └──┬────┘
│ │ │
┌───▼───────▼───────▼───┐
│ Block I/O Layer │ Cozi de request-uri, schedulere I/O
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ Device Drivers │ SCSI, NVMe, AHCI
└───────────┬───────────┘
│
┌───▼───┐
│ Disc │
└───────┘
Sisteme de fișiere speciale (fără disc):
proc → /proc (informații kernel/proces)
sysfs → /sys (model de dispozitive, drivere)
tmpfs → /tmp, /dev/shm (RAM-backed)
devtmpfs → /dev (noduri de dispozitiv)
cgroup → /sys/fs/cgroup (control groups)
7.2 Obiectele fundamentale VFS¶
/* 1. SUPERBLOCK — reprezintă un filesystem montat */
struct super_block {
struct list_head s_list; /* Lista tuturor superblock-urilor */
dev_t s_dev; /* Dispozitivul bloc */
unsigned long s_blocksize; /* Dimensiunea blocului (ex. 4096) */
struct file_system_type *s_type; /* Tipul FS (ext4, xfs, ...) */
const struct super_operations *s_op; /* Operații: alloc_inode, write_inode, sync_fs */
struct dentry *s_root; /* Dentry-ul rădăcină */
/* ... */
};
/* 2. INODE — reprezintă un fișier pe disc (metadate) */
struct inode {
umode_t i_mode; /* Tip + permisiuni (rwxrwxrwx) */
kuid_t i_uid; /* Owner UID */
kgid_t i_gid; /* Owner GID */
unsigned int i_flags;
const struct inode_operations *i_op; /* Operații: create, lookup, link, unlink, mkdir */
struct super_block *i_sb; /* Superblock-ul părinte */
unsigned long i_ino; /* Inode number */
atomic_t i_count; /* Referințe */
loff_t i_size; /* Dimensiunea fișierului */
struct timespec64 __i_atime; /* Access time */
struct timespec64 __i_mtime; /* Modification time */
struct timespec64 __i_ctime; /* Change time */
const struct file_operations *i_fop; /* Operații pe fișier deschis */
struct address_space *i_mapping; /* Page cache pentru conținut */
/* ... */
};
/* 3. DENTRY — intrare în director (legătura nume ↔ inode) */
struct dentry {
struct dentry *d_parent; /* Directorul părinte */
struct qstr d_name; /* Numele fișierului */
struct inode *d_inode; /* Inode-ul asociat */
const struct dentry_operations *d_op;
struct super_block *d_sb;
/* ... */
};
/* Dentry-urile sunt agresiv cache-uite (dcache) pentru performanță */
/* 4. FILE — un fișier deschis (instanță per descriptor) */
struct file {
struct path f_path; /* Dentry + vfsmount */
struct inode *f_inode;
const struct file_operations *f_op; /* read, write, ioctl, mmap, open, release */
atomic_long_t f_count; /* Referințe */
unsigned int f_flags; /* O_RDONLY, O_WRONLY, O_NONBLOCK, ... */
fmode_t f_mode;
loff_t f_pos; /* Poziția curentă (offset) */
/* ... */
};
7.3 Operații pe fișiere — file_operations¶
/* Structura centrală care leagă syscall-urile de implementare: */
struct file_operations {
struct module *owner;
loff_t (*llseek)(struct file *, loff_t, int);
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter)(struct kiocb *, struct iov_iter *);
ssize_t (*write_iter)(struct kiocb *, struct iov_iter *);
int (*iterate_shared)(struct file *, struct dir_context *);
__poll_t (*poll)(struct file *, struct poll_table_struct *);
long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
long (*compat_ioctl)(struct file *, unsigned int, unsigned long);
int (*mmap)(struct file *, struct vm_area_struct *);
int (*open)(struct inode *, struct file *);
int (*flush)(struct file *, fl_owner_t);
int (*release)(struct inode *, struct file *);
int (*fsync)(struct file *, loff_t, loff_t, int);
int (*fasync)(int, struct file *, int);
int (*flock)(struct file *, int, struct file_lock *);
/* ... */
};
/* Fluxul complet: open() + read() + close() */
/*
* 1. sys_open("/home/user/file.txt", O_RDONLY):
* - Parsare path: "home" → "user" → "file.txt" (dentry lookup)
* - Verificare permisiuni
* - Alocă struct file, asociază cu inode-ul găsit
* - Alocă file descriptor (int fd) în tabela files_struct
* - Apelează f_op->open() (dacă există)
* - Returnează fd
*
* 2. sys_read(fd, buf, 4096):
* - Găsește struct file din files_struct[fd]
* - Apelează f_op->read(file, buf, 4096, &file->f_pos)
* - Implementarea citește din page cache (sau de pe disc dacă lipsește)
* - copy_to_user() transferă datele în bufferul userspace
* - Avansează f_pos
* - Returnează nr bytes citiți
*
* 3. sys_close(fd):
* - Decrementează referința pe struct file
* - Dacă f_count ajunge la 0: apelează f_op->release()
* - Eliberează slotul din tabela de descriptori
*/
7.4 Sistemele proc și sysfs¶
/* /proc — informații despre procese și kernel */
/* /proc/cpuinfo, /proc/meminfo, /proc/interrupts, /proc/<pid>/... */
/* Creare intrare în /proc: */
#include <linux/proc_fs.h>
static int my_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "Hello from /proc/myfile\n");
seq_printf(m, "Jiffies: %lu\n", jiffies);
return 0;
}
static int my_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, my_proc_show, NULL);
}
static const struct proc_ops my_proc_ops = {
.proc_open = my_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
/* La inițializare modulului: */
proc_create("myfile", 0444, NULL, &my_proc_ops);
/* La cleanup: */
remove_proc_entry("myfile", NULL);
/* /sys — modelul de dispozitive și atribute */
/* /sys/class/net/eth0/speed, /sys/bus/pci/devices/..., /sys/devices/... */
/* Creare atribut sysfs (pentru un driver): */
static ssize_t my_attr_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%d\n", my_value);
}
static ssize_t my_attr_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
sscanf(buf, "%d", &my_value);
return count;
}
static DEVICE_ATTR(my_attr, 0644, my_attr_show, my_attr_store);
/* Accesibil ca: /sys/devices/.../my_attr */
/* Citire: cat /sys/.../my_attr */
/* Scriere: echo 42 > /sys/.../my_attr */
8. Mecanisme de sincronizare în kernel¶
8.1 De ce sincronizare?¶
Kernel-ul Linux este preemptiv și multi-procesor (SMP). Mai multe contexte de execuție pot accesa simultan aceleași structuri de date: mai multe nuclee CPU, preemțiunea kernel, întreruperile. Fără sincronizare, apar race conditions și corupere de date.
8.2 Atomic operations¶
Cele mai simple primitive, implementate cu instrucțiuni hardware atomice:
#include <linux/atomic.h>
atomic_t counter = ATOMIC_INIT(0);
atomic_set(&counter, 5); /* counter = 5 */
atomic_inc(&counter); /* counter++ (atomic) */
atomic_dec(&counter); /* counter-- (atomic) */
atomic_add(3, &counter); /* counter += 3 */
int val = atomic_read(&counter); /* citire */
/* Test-and-set: */
if (atomic_dec_and_test(&counter)) {
/* counter a ajuns la 0 */
}
/* Compare-and-swap: */
int old = atomic_cmpxchg(&counter, expected, new_val);
/* Pe x86, atomic_inc se compilează la: */
/* lock inc dword [rdi] ← prefixul LOCK asigură atomicitate pe bus */
/* Pe ARM64: LDXR/STXR (Load/Store Exclusive) */
8.3 Spinlocks¶
Spinlock-ul blochează prin așteptare activă (busy-wait). Potrivit pentru secțiuni critice scurte și contexte unde nu poți dormi (întreruperi).
#include <linux/spinlock.h>
DEFINE_SPINLOCK(my_lock); /* Declarare și inițializare */
/* sau: spinlock_t my_lock; spin_lock_init(&my_lock); */
/* Utilizare de bază: */
spin_lock(&my_lock);
/* ... secțiune critică ... */
/* Regula: FII RAPID! Nu dormi, nu apela funcții care pot dormi! */
spin_unlock(&my_lock);
/* Variante cu dezactivare întreruperi: */
unsigned long flags;
spin_lock_irqsave(&my_lock, flags); /* Salvează + dezactivează IRQ */
/* ... secțiune critică protejată și de întreruperi ... */
spin_unlock_irqrestore(&my_lock, flags); /* Restaurează starea IRQ */
/* spin_lock_irqsave este NECESAR când secțiunea critică poate fi
* accesată atât din context de proces CÂT ȘI din ISR.
* Altfel, se poate ajunge la deadlock:
* 1. Procesul ia lock-ul
* 2. Întrerupere pe același CPU
* 3. ISR încearcă să ia lock-ul → deadlock (se așteaptă pe sine) */
/* Variante: */
spin_lock_bh(&my_lock); /* Dezactivează doar bottom-halves (softirq) */
spin_lock_irq(&my_lock); /* Dezactivează IRQ fără a salva flags */
8.4 Mutexuri¶
Mutex-ul poate dormi (sleep) în așteptare. Potrivit pentru secțiuni critice lungi în context de proces.
#include <linux/mutex.h>
DEFINE_MUTEX(my_mutex);
mutex_lock(&my_mutex); /* Blochează (poate dormi!) */
/* ... secțiune critică (poate include I/O, alocare memorie, etc.) ... */
mutex_unlock(&my_mutex);
/* Non-blocking: */
if (mutex_trylock(&my_mutex)) {
/* Am obținut lock-ul */
mutex_unlock(&my_mutex);
} else {
/* Lock-ul era deja ținut de altcineva */
}
/* Interruptible (poate fi întrerupt de semnal): */
if (mutex_lock_interruptible(&my_mutex)) {
return -EINTR; /* Am fost întrerupți de un semnal */
}
/* REGULĂ: mutex-urile NU se pot folosi în context de întrerupere! */
/* (deoarece nu poți dormi în ISR) */
8.5 Semafoare¶
#include <linux/semaphore.h>
/* Semafor binar (echivalent mutex, dar fără ownership tracking): */
DEFINE_SEMAPHORE(my_sem, 1);
down(&my_sem); /* P() — decrementează, doarme dacă 0 */
up(&my_sem); /* V() — incrementează, trezește un waiter */
/* Semafor de numărare (permite N accese simultane): */
DEFINE_SEMAPHORE(pool_sem, 5); /* max 5 accese concurente */
8.6 Reader-Writer Locks¶
Optimizează scenariile cu mulți cititori și puțini scriitori:
#include <linux/rwlock.h>
/* RW Spinlock: */
DEFINE_RWLOCK(my_rwlock);
read_lock(&my_rwlock); /* Mai mulți cititori simultan */
/* ... citire date partajate ... */
read_unlock(&my_rwlock);
write_lock(&my_rwlock); /* Exclusiv — nici cititori, nici scriitori */
/* ... modificare date ... */
write_unlock(&my_rwlock);
/* RW Semaphore (poate dormi): */
#include <linux/rwsem.h>
DECLARE_RWSEM(my_rwsem);
down_read(&my_rwsem); /* Citire partajată */
up_read(&my_rwsem);
down_write(&my_rwsem); /* Scriere exclusivă */
up_write(&my_rwsem);
8.7 RCU (Read-Copy-Update)¶
Cea mai sofisticată primitivă de sincronizare din kernel. Permite citiri fără niciun lock sau barieră, cu overhead zero. Scriitorii lucrează pe o copie și comută atomic pointerul:
#include <linux/rcupdate.h>
#include <linux/rculist.h>
struct my_data {
int value;
struct rcu_head rcu; /* pentru eliberare amânată */
};
struct my_data __rcu *global_ptr;
/* === Cititor (extrem de rapid, fără lock) === */
rcu_read_lock(); /* Dezactivează preemțiunea */
struct my_data *p = rcu_dereference(global_ptr); /* Citire cu barieră */
int v = p->value; /* Folosește datele */
rcu_read_unlock();
/* Nu se pot face operații blocante (sleep) între rcu_read_lock/unlock! */
/* === Scriitor === */
struct my_data *old, *new;
new = kmalloc(sizeof(*new), GFP_KERNEL);
new->value = 42;
old = rcu_dereference_protected(global_ptr, lockdep_is_held(&my_mutex));
rcu_assign_pointer(global_ptr, new); /* Comutare atomică pointer */
/* Așteaptă finalizarea tuturor cititorilor care au văzut old ptr */
synchronize_rcu(); /* Poate dormi! */
kfree(old); /* Acum e sigur de eliberat */
/* Alternativ, eliberare asincronă (fără a bloca scriitorul): */
call_rcu(&old->rcu, my_rcu_callback);
/* Callback-ul va fi apelat după grace period */
8.8 Diagrama de decizie: ce mecanism de sincronizare?¶
Ai nevoie de sincronizare?
│
├── Operație simplă pe un singur întreg?
│ └── atomic_t / atomic64_t
│
├── Secțiune critică scurtă (< câteva μs)?
│ ├── Poate fi accesată din ISR?
│ │ └── spin_lock_irqsave()
│ └── Doar context de proces?
│ └── spin_lock()
│
├── Secțiune critică lungă (I/O, alocare, sleep)?
│ └── mutex_lock()
│
├── Mulți cititori, puțini scriitori?
│ ├── Citirile trebuie să fie ultra-rapide?
│ │ └── RCU (zero overhead la citire)
│ └── Citirile pot tolera puțin overhead?
│ └── rwlock / rwsem
│
├── Limitare nr. accese concurente?
│ └── semaphore
│
└── Comunicare între fire (notificare eveniment)?
└── completion (wait_for_completion / complete)
9. Întreruperi, Softirqs, Tasklets și Workqueues¶
9.1 Modelul top-half / bottom-half¶
Procesarea întreruperilor este împărțită în două faze:
IRQ hardware
│
▼
┌─────────────────────────────────────────┐
│ TOP HALF (hardirq handler) │
│ - Rulează cu întreruperea dezactivată │
│ - TREBUIE să fie minim și rapid │
│ - Citește registre hardware, ack IRQ │
│ - Programează bottom-half dacă e nevoie │
│ - NU poate dormi, NU alocă cu GFP_KERNEL│
└─────────────┬───────────────────────────┘
│ Programează bottom-half
▼
┌─────────────────────────────────────────┐
│ BOTTOM HALF (procesare amânată) │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐│
│ │ Softirq │ │ Tasklet │ │Workqueue ││
│ │ (static, │ │ (dinamic, │ │(context ││
│ │ per-CPU, │ │ bazat pe │ │de proces,││
│ │ nu doarme│ │ softirq, │ │POATE ││
│ │ concurent│ │ nu doarme,│ │dormi, ││
│ │ pe CPU- │ │ serializat│ │concurent)││
│ │ uri diff)│ │ pe un tip)│ │ ││
│ └──────────┘ └───────────┘ └──────────┘│
└─────────────────────────────────────────┘
9.2 Înregistrarea unui handler de întrerupere¶
#include <linux/interrupt.h>
/* Handler (top-half): */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
u32 status;
/* Citește registrul de stare al dispozitivului */
status = readl(dev->regs + STATUS_REG);
/* Verifică dacă întreruperea e de la dispozitivul nostru */
if (!(status & IRQ_PENDING_BIT))
return IRQ_NONE; /* Nu e a noastră (shared IRQ) */
/* Confirmă întreruperea (clear pending bit) */
writel(IRQ_PENDING_BIT, dev->regs + STATUS_REG);
/* Salvează datele minime (ex. citește FIFO) */
dev->rx_data = readl(dev->regs + DATA_REG);
/* Programează bottom-half pentru procesare ulterioară */
tasklet_schedule(&dev->rx_tasklet);
return IRQ_HANDLED; /* Am procesat întreruperea */
}
/* Înregistrare la inițializare: */
int ret = request_irq(irq_num,
my_irq_handler,
IRQF_SHARED, /* Partajat cu alte dispozitive */
"my_device", /* Nume (vizibil în /proc/interrupts) */
&my_dev); /* Cookie transmis handler-ului */
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", irq_num);
return ret;
}
/* Dezînregistrare la cleanup: */
free_irq(irq_num, &my_dev);
/* Flag-uri importante: */
#define IRQF_SHARED /* Linia IRQ este partajată */
#define IRQF_ONESHOT /* Nu re-activează IRQ până handler-ul threaded termină */
#define IRQF_TRIGGER_RISING /* Trigger pe front crescător */
#define IRQF_TRIGGER_FALLING /* Trigger pe front descrescător */
#define IRQF_TRIGGER_HIGH /* Trigger pe nivel HIGH */
#define IRQF_TRIGGER_LOW /* Trigger pe nivel LOW */
9.3 Threaded IRQ handlers¶
Alternativă modernă: handler-ul bottom-half rulează într-un kernel thread dedicat, putând dormi:
/* request_threaded_irq combină top-half + threaded bottom-half: */
ret = request_threaded_irq(irq_num,
my_hardirq_handler, /* Top-half (rapid, opțional) */
my_thread_handler, /* Bottom-half (thread, poate dormi) */
IRQF_ONESHOT,
"my_device",
&my_dev);
/* Top-half (opțional — poate fi NULL): */
static irqreturn_t my_hardirq_handler(int irq, void *dev_id)
{
/* Ack rapid */
return IRQ_WAKE_THREAD; /* Trezește thread-ul bottom-half */
}
/* Thread handler (poate dormi, poate folosi mutex, I²C, SPI): */
static irqreturn_t my_thread_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
/* Poate face operații lente: */
mutex_lock(&dev->lock);
i2c_smbus_read_byte_data(dev->client, REG_ADDR); /* I²C = poate dormi! */
mutex_unlock(&dev->lock);
return IRQ_HANDLED;
}
9.4 Softirqs¶
Softirq-urile sunt mecanismul de bottom-half de cel mai jos nivel. Sunt statice (definite la compilare), per-CPU (pot rula concurent pe CPU-uri diferite), și nu pot dormi.
/* Softirq-urile definite în kernel (include/linux/interrupt.h): */
enum {
HI_SOFTIRQ = 0, /* Tasklets de prioritate înaltă */
TIMER_SOFTIRQ, /* Timere (hrtimer) */
NET_TX_SOFTIRQ, /* Rețea — transmisie */
NET_RX_SOFTIRQ, /* Rețea — recepție (NAPI) */
BLOCK_SOFTIRQ, /* Block I/O completion */
IRQ_POLL_SOFTIRQ, /* IRQ polling */
TASKLET_SOFTIRQ, /* Tasklets normale */
SCHED_SOFTIRQ, /* Scheduler load balancing */
HRTIMER_SOFTIRQ, /* High-resolution timers */
RCU_SOFTIRQ, /* RCU processing */
NR_SOFTIRQS /* Total: ~10 */
};
/* Softirq-urile nu se adaugă dinamic — sunt rezervate subsistemelor core.
* Driverele folosesc tasklets sau workqueues. */
9.5 Tasklets¶
Tasklet-urile sunt construite peste softirq. Sunt serializate per instanță (un tasklet nu rulează pe două CPU-uri simultan), dar diferite tasklets pot rula concurent.
/* Declarare: */
static void my_tasklet_func(unsigned long data)
{
struct my_device *dev = (struct my_device *)data;
/* Procesare bottom-half — NU poate dormi! */
process_received_data(dev);
}
DECLARE_TASKLET(my_tasklet, my_tasklet_func, (unsigned long)&my_dev);
/* sau din kernel 5.9+: */
DECLARE_TASKLET(my_tasklet, my_tasklet_callback);
/* Programare (din hardirq sau alt context): */
tasklet_schedule(&my_tasklet);
/* Dezactivare / distrugere: */
tasklet_disable(&my_tasklet); /* Așteaptă finalizare, previne re-execuție */
tasklet_kill(&my_tasklet); /* Așteaptă finalizare, eliberează */
9.6 Workqueues¶
Workqueue-urile rulează în context de kernel thread — pot dormi, pot folosi mutexuri, pot face operații I/O complexe.
#include <linux/workqueue.h>
/* Funcția work: */
static void my_work_func(struct work_struct *work)
{
struct my_device *dev = container_of(work, struct my_device, my_work);
/* POATE dormi, POATE folosi mutex, POATE aloca cu GFP_KERNEL */
mutex_lock(&dev->lock);
/* ... procesare complexă ... */
mutex_unlock(&dev->lock);
}
/* Declarare: */
struct my_device {
struct work_struct my_work;
/* ... */
};
INIT_WORK(&dev->my_work, my_work_func);
/* Programare pe workqueue-ul global implicit: */
schedule_work(&dev->my_work);
/* Programare cu delay: */
INIT_DELAYED_WORK(&dev->delayed_work, my_delayed_func);
schedule_delayed_work(&dev->delayed_work, msecs_to_jiffies(100));
/* Workqueue dedicat (pentru izolare sau control): */
struct workqueue_struct *my_wq;
my_wq = alloc_workqueue("my_wq", WQ_UNBOUND | WQ_HIGHPRI, 0);
queue_work(my_wq, &dev->my_work);
/* Cleanup: */
cancel_work_sync(&dev->my_work); /* Anulează + așteaptă finalizare */
destroy_workqueue(my_wq);
10. Temporizatoare și gestiunea timpului¶
10.1 Surse de timp în kernel¶
┌──────────────────────────────────────────────────┐
│ Surse de ceas hardware │
│ │
│ x86: │
│ TSC (Time Stamp Counter) — cel mai precis │
│ HPET (High Precision Event Timer) │
│ APIC Timer (per-CPU, configurat de kernel) │
│ PIT 8253 (legacy, 1.193 MHz, rar folosit) │
│ ACPI PM Timer (power management, 3.58 MHz) │
│ │
│ ARM: │
│ Generic Timer (architected, per-CPU) │
│ SysTick (Cortex-M, 24-bit down-counter) │
│ Platform-specific timers (în device tree) │
└──────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Abstractizare kernel: clocksource + clockevents │
│ │
│ clocksource: furnizează valoarea curentă a timpului│
│ → TSC (pe x86), arch_counter (pe ARM64) │
│ │
│ clockevents: generează întreruperi la momente │
│ viitoare (one-shot sau periodic) │
│ → LAPIC timer (x86), Generic Timer (ARM) │
└──────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────┐
│ Kernel time subsystem │
│ │
│ jiffies: contor global, incrementat la fiecare │
│ tick (HZ ori pe secundă, tipic 250/1000) │
│ │
│ ktime_t: timp de înaltă rezoluție (nanosecunde) │
│ │
│ Timere: │
│ - Timer wheel (jiffies-based, rezoluție ~1-4ms) │
│ - hrtimers (nanosecond resolution) │
│ │
│ Tickless (NO_HZ): │
│ - NO_HZ_IDLE: oprește tick-ul pe CPU-uri idle │
│ - NO_HZ_FULL: oprește tick-ul și pe CPU active │
│ (pentru workloads latency-sensitive│
└──────────────────────────────────────────────────┘
10.2 Jiffies și HZ¶
/* jiffies = contor global de tick-uri de la boot */
/* HZ = frecvența tick-ului (configurat la compilare) */
/* HZ=250 → 1 jiffy = 4 ms */
/* HZ=1000 → 1 jiffy = 1 ms */
#include <linux/jiffies.h>
unsigned long now = jiffies;
unsigned long future = jiffies + HZ; /* peste 1 secundă */
unsigned long later = jiffies + 5 * HZ; /* peste 5 secunde */
/* Comparație sigură (gestionează overflow pe 32 biți): */
if (time_after(jiffies, deadline)) {
/* Deadline depășit */
}
if (time_before(jiffies, deadline)) {
/* Încă nu am ajuns la deadline */
}
/* Conversii: */
unsigned long ms = jiffies_to_msecs(jiffies);
unsigned long j = msecs_to_jiffies(500); /* 500ms → jiffies */
10.3 Timere clasice (timer wheel)¶
#include <linux/timer.h>
static struct timer_list my_timer;
/* Callback (rulează în context softirq — NU poate dormi!): */
static void my_timer_callback(struct timer_list *t)
{
/* Procesare periodică */
pr_info("Timer fired! jiffies=%lu\n", jiffies);
/* Re-armare pentru periodicitate: */
mod_timer(&my_timer, jiffies + HZ); /* Peste 1 secundă */
}
/* Inițializare: */
timer_setup(&my_timer, my_timer_callback, 0);
/* Pornire (prima expirare peste 2 secunde): */
mod_timer(&my_timer, jiffies + 2 * HZ);
/* Oprire: */
del_timer_sync(&my_timer); /* Așteaptă finalizarea callback-ului dacă rulează */
10.4 High-resolution timers (hrtimers)¶
#include <linux/hrtimer.h>
#include <linux/ktime.h>
static struct hrtimer my_hrtimer;
/* Callback (rulează în context hardirq sau softirq, NU doarme): */
static enum hrtimer_restart my_hrtimer_callback(struct hrtimer *timer)
{
ktime_t now = ktime_get();
pr_info("hrtimer fired at %lld ns\n", ktime_to_ns(now));
/* Re-armare: */
hrtimer_forward_now(timer, ktime_set(0, 500000)); /* 500 μs */
return HRTIMER_RESTART;
/* SAU: return HRTIMER_NORESTART; pentru one-shot */
}
/* Inițializare și pornire: */
hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
my_hrtimer.function = my_hrtimer_callback;
hrtimer_start(&my_hrtimer, ktime_set(1, 0), HRTIMER_MODE_REL); /* 1 sec */
/* Oprire: */
hrtimer_cancel(&my_hrtimer);
10.5 Delay-uri și sleep-uri în kernel¶
/* === Delay-uri active (busy-wait) — context de întrerupere OK === */
#include <linux/delay.h>
ndelay(100); /* ~100 nanosecunde */
udelay(50); /* ~50 microsecunde */
mdelay(10); /* ~10 milisecunde (EVITĂ! Blochează CPU) */
/* udelay e implementat cu o buclă calibrată (BogoMIPS pe x86)
* sau citirea TSC/counters pe sisteme moderne. */
/* === Sleep-uri (cedează CPU-ul) — DOAR context de proces === */
#include <linux/delay.h>
usleep_range(100, 200); /* 100-200 μs (hrtimer-based, precis) */
msleep(50); /* ~50 ms (timer wheel, rezoluție ~1-4ms) */
msleep_interruptible(1000); /* 1s, poate fi întrerupt de semnal */
ssleep(2); /* 2 secunde */
/* Regulă de alegere:
* < 10 μs → udelay() (busy-wait, orice context)
* 10 μs - 20ms → usleep_range() (precis, context proces)
* > 20ms → msleep() (eficient, context proces) */
Referințe și resurse¶
Cărți fundamentale¶
- “Linux Kernel Development” — Robert Love (excelentă introducere)
- “Understanding the Linux Kernel” — Bovet & Cesati (referință detaliată)
- “Linux Device Drivers” (LDD3) — Corbet, Rubini & Kroah-Hartman (drivere, free online)
- “Professional Linux Kernel Architecture” — Wolfgang Mauerer (arhitectură profundă)
Resurse online¶
- Documentația oficială:
Documentation/din sursa kernel-ului - https://www.kernel.org/doc/html/latest/ — documentație generată
- https://elixir.bootlin.com/linux/latest/source — browser cod sursă
- https://lwn.net — articole tehnice despre dezvoltarea kernel-ului
Instrumente esențiale¶
# Navigare cod sursă
cscope -R # Indexare + navigare interactivă
ctags -R # Tag-uri pentru editor (vim, emacs)
# Build și test
make -j$(nproc) # Compilare paralelă
make M=drivers/my_drv/ # Compilare doar un director
make modules # Compilare doar modulele
# Debugging
dmesg # Mesajele kernel (printk, pr_info, dev_err)
dmesg -w # Urmărire în timp real
cat /proc/interrupts # Starea întreruperilor
cat /proc/meminfo # Starea memoriei
cat /proc/slabinfo # Starea alocatoarelor slab
cat /proc/vmstat # Statistici VM
cat /proc/schedstat # Statistici scheduler
perf stat ./program # Contori de performanță hardware
ftrace # Tracer intern kernel (function, irq, sched)
Partea I din cursul avansat de Sisteme de Operare. Continuă cu Partea II: Module kernel, drivere de dispozitiv, modelul de bus, networking, debugging și securitate.