Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
Lecture 1:
The kernel. Structure. Process schedulers.
Version: 3
System programming
Kernel
Drivers
Console
Bootloader
This IS operating system
Player
Browser
Compiler
This IS NOT operating system
OS - minimal subset of programs
Kernel
Console
Kernel space
Player
Browser
Compiler
User space
System calls
Kernel - hardware manager
read write open close fork ...
User
Buttons
Electrical signal
Interrupt
Kernel
Device controller initiates an electrical signal
A button is pressed
Processor got the signal and stopped the current task
A handler in the kernel is called
Handler knows interrupt number, plans further processing
Interrupts
Kernel
Processing ...
Who starts the kernel?
Bootloader
Who starts the bootloader?
Where is BIOS?
God ... what is ROM now?
Why does not BIOS start the kernel directly? Why is a bootloader needed?
It does not know what a filesystem there is, and can not find kernel files in there
1 point
The lamp on a slide means a scored question
Processes
Hardware
Time
Filesystem
IPC
Network
Users
Data structures
Virtualization
/**
* 30.09.2018
* #include/linux/sched.h
* 618 lines.
*/
struct task_struct {
struct thread_info thread_info;
long state;
void *stack;
atomic_t usage;
unsigned int cpu;
int prio;
struct mm_struct *mm;
int exit_state;
int exit_code;
int exit_signal;
pid_t pid;
struct task_struct *parent;
struct list_head children;
u64 start_time;
const struct cred *cred;
struct files_struct *files;
struct thread_struct thread;
};
long state;
atomic_t usage;
unsigned int cpu;
int prio;
struct mm_struct *mm;
int exit_state;
int exit_code;
int exit_signal;
pid_t pid;
struct task_struct *parent;
struct list_head children;
const struct cred *cred;
struct files_struct *files;
pid_t
getpid(void);
pid_t
getppid(void);
vladislav$> cat /proc/sys/kernel/pid_max 32768
fork()
Ready to run
Running
schedule()
Terminated
do_exit()
preemtion
Waiting an event
Wait an event
The event or signal
Thread
Process
Periodical and onetime tasks
Hardware time source
Oscillator
Oscillator inside
Polished crystal
Raw crystal
Oscillator inside
Polished crystal
HZ = #
1s
Too large HZ
Damn ... when is the next nanosecond?
vladislav$> grep 'CONFIG_HZ=' /boot/config-$(uname -r) CONFIG_HZ=250
Real Time Clock
Detected 2400.131 MHz processor. Calibrating delay loop... 4799.56 BogoMIPS
Pulse Per Second
Source of signal
Atomic clock
Precision - picoseconds
/**
* 30.09.2018
* 33 virtual functions, 149 lines.
*/
struct file_operations {
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 *);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*flock) (struct file *, int, struct file_lock *);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
struct super_block {
struct file_system_type *s_type;
const struct super_operations *s_op;
int s_count;
struct list_head s_mounts;
};
read(fd)
syscall
struct inode
inode->read()
ext4_read()
Mutexes, semaphores
Message queues
Shared memory
Pipe
Domain sockets
Application
Transport
Internet
Link
Level:
Model IP + TCP/UDP
/**
* 30.09.2018.
* 39 lines.
*/
struct cred {
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
struct user_struct *user; /* real user ID subscription */
};
struct unit {
int hp;
int damage;
struct unit *next, *prev;
char name[0];
};
struct unit_list {
struct unit *first;
struct unit *last;
};
void
unit_list_push_back(struct unit_list *list,
struct unit *u);
void
unit_list_push_front(struct unit_list *list,
struct unit *u);
void
unit_list_remove(struct unit_list *list,
struct unit *u);
bool
unit_list_is_empty(const struct unit_list *list);
void
unit_list_create(struct unit_list *list);
struct building {
int hp;
struct unit_list units;
struct building *next, *prev;
char name[0];
};
struct building_list {
struct building *first;
struct building *last;
};
void
building_list_push_back(struct building_list *list,
struct building *b);
void
building_list_push_front(struct building_list *list,
struct building *b);
void
building_list_remove(struct building_list *list,
struct building *b);
bool
building_list_is_empty(const struct building_list *list);
void
building_list_create(struct building_list *list);
Need two lists - code duplication. Can C++ help?
template<class parent>
struct list_element {
parent *next, *prev;
char name[0];
};
template<class parent>
struct list {
parent *first;
parent *last;
};
void
list_push_back(struct list *list,
struct list_element *e);
void
list_push_front(struct list *list,
struct list_element *e);
void
list_remove(struct list *list,
struct list_element *e);
bool
list_is_empty(const struct list *list);
void
list_create(struct list *list);
struct building : public list_element<struct building> {
int hp;
struct unit_list units;
char name[0];
};
struct unit : public list_element<struct unit> {
int hp;
int damage;
char name[0];
};
Can not store in multiple lists because of inheritance. Only one list_element parent is possible. Lets try STL?
template<class object>
struct list_element {
object *obj;
struct list_element<object> *next, *prev;
};
template<class object>
struct list {
struct list_element<object> *first, *last;
};
void
list_push_back(struct list *list,
struct object e);
void
list_push_front(struct list *list,
struct object e);
void
list_remove(struct list *list,
struct object e);
bool
list_is_empty(const struct list *list);
void
list_create(struct list *list);
struct building {
int hp;
struct unit_list units;
char name[0];
};
struct unit {
int hp;
int damage;
char name[0];
};
Pointer on pointer - double lookup into the memory - twice longer access
struct list_element {
struct list_element *next, *prev;
};
struct list {
struct list_element *first, *last;
};
void
list_push_back(struct list *list,
struct list_element *e);
void
list_push_front(struct list *list,
struct list_element *e);
void
list_remove(struct list *list,
struct list_element *e);
bool
list_is_empty(const struct list *list);
void
list_create(struct list *list);
struct unit {
int hp;
int damage;
struct list_element in_army;
struct list_element in_building;
char name[0];
};
struct building {
int hp;
struct list units;
struct list_element in_city;
char name[0];
};
A no-data list. Works with no-data elements.
When a structure wants to participate in a list, it adds the empty list element as an attribute.
The list stores pointers on struct unit attributes
struct list army;
struct building house;
struct unit unit;
list_insert(&army, &unit.in_army);
list_insert(&house.units, &unit.in_building);
/* ... */
struct list_element *e;
struct unit *u;
e = list_first(&army);
u = container_of(e, struct unit, in_army);
e = list_first(&house.units);
u = container_of(e, struct unit, in_building);
Knowing an attribute offset, it is possible to get the parent structure pointer
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
struct list_head {
struct list_head *next, *prev;
};
Kernel list
struct my_struct {
struct list_head base;
int a;
int b;
const char *c;
};
struct my_struct ms1, ms2;
INIT_LIST_HEAD(&ms1);
list_add(&ms1, &ms2);
struct rb_node {
unsigned long __rb_parent_color;
struct rb_node *rb_right;
struct rb_node *rb_left;
};
struct rb_root {
struct rb_node *rb_node;
};
Kernel red-black tree
struct rb_root tree = RB_ROOT;
struct my_struct {
struct rb_node node;
int a;
int b;
const char *c;
};
struct my_struct ms1, ms2;
rb_insert_color(&tree, &ms1);
rb_insert_color(&tree, &ms2);
When the list element is the first attribute, it is not even necessary to access it directly. Works like C 'inheritance'.
Cooperative multitasking -
voluntary yield
Preemptive multitasking -
mandatory preemtion
dumb scheduler
O(1) scheduler
Completely Fair Scheduler
I/O Bound
CPU Bound
Time
Disk
Interactive applications
Network
Transaction processing
Calculations
What are two types of multitasking?
Cooperative and preemptive
1 point
int
getpriority(int which, id_t who);
int
setpriority(int which, id_t who, int prio);
int
nice(int inc);
vladislav$> ps -el
UID PID PRI NI TTY CMD
0 1 37 0 ?? /sbin/launchd
Real-time priority
Nice
Timeslice / quantum
start
interrupt
nice -- execute a utility with an altered scheduling priority renice -- alter priority of running processes chrt -- manipulate the real-time attributes of a process
Example
Video conversion
Chess with AI
Will end earlier
Low interactivity
Priority
Priority
Cache losses, will end later
Fast reaction
Time quantum in Linux - relative value
10 мс
10 мс
50 %
50 %
Process can consume time quantum partially, and share the rest with others
Completely Fair Scheduler
N - process count,
perfect processor sharing - 1/N -> 0, infinitely often switching
Perfect scheduler
/**
* 30.09.2018.
* 36 lines.
*/
struct sched_entity {
struct load_weight load;
unsigned long runnable_weight;
struct rb_node run_node;
struct list_head group_node;
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
u64 nr_migrations;
struct sched_entity *parent;
};
struct load_weight load;
unsigned long runnable_weight;
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
/*
* Pick the next process, keeping these things in mind, in this order:
* 1) keep things fair between processes/task groups
* 2) pick the "next" process, since someone really wants that to run
* 3) pick the "last" process, for cache locality
* 4) do not run the "skip" process, if something else is available
*/
static struct sched_entity *
pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
struct sched_entity *left = __pick_first_entity(cfs_rq);
/* .... 47 lines of code. */
}
struct rb_node run_node;
Kernel != OS. The operating system is built on top of the kernel.
There are things below the kernel like BIOS, bootloader, hypervisor.
Kernel has many components like drivers, time tracking, process scheduling, networking, own data structure libraries.
Process/thread scheduler makes the system look like all runs in parallel, but in fact the programs just work in turns for very small durations which we don't notice normally. The scheduler's main job is to make those turns fair.
Merge sort using C/C++ coroutines
Files are stored on disk. They contain ASCII encoded numbers in arbitrary order, unsorted. You need to sort each file and then merge them into a big one. In other words, just do the merge sort.
Implement C coroutines, a scheduler, and use them. More information by the link:
Points: 15 - 25.
Deadline: 2 weeks.
Penalty: -1 for each day after deadline, max -10
Publish your solution on Github and give me the link. Assessment: any way you want - messengers, calls, emails.
Process. Work modes, memory, resources. Interrupts. Communication with the kernel. System calls.
Press on the heart, if like the lecture
Lectures: slides.com/gerold103/decks/sysprog_eng
Next time:
By Vladislav Shpilevoy
The kernel. Process and its representation in the kernel. Process states, lifecycle. Types of process scheduling: preemptive and cooperative. Process schedulers, and IO Bound vs Processor Bound processes, details of scheduling. 'Nice' value and priorities. Timeslice - scheduling atom. CFS scheduler.
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.