forked from FourierSignal/Hello-World
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathOS_concepts_FAQs
More file actions
1285 lines (824 loc) · 56.4 KB
/
Copy pathOS_concepts_FAQs
File metadata and controls
1285 lines (824 loc) · 56.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
HOW DETERMINISM ACHIEVED IN RTOS...??
RTOS is designed for determinism(how quickly it can respond to events) vs GPOS for High performance( how much amount of work done/time)
minimum Interrupt Latency
minimum thread switching Latency
pre-emptible kernel vs non-pre emptible kernel:
in non-preemtive kernel - a high-priority user thread can never preempt a kernel call, but must instead wait for the entire call to
complete – even if the call was invoked by the lowest-priority process
==> unpredictable delays and prevents critical activities from completing on time.
In an RTOS, on the other hand, kernel operations are preemptible. There are stil windows of time in which preemption may
not occur, but in a well-designed RTOS,those intervals are extremely brief.
Moreover RTOS will impose an upper bound on how long preemption is held off and interrupts disabled; this
allows developers to ascertain worst-case latencies.
Only services with a short execution path should be included in the kernel itself. Any operations that require significant work
(for instance, process loading) must be assigned to external processes or threads.
In an RTOS the kernel is kept very simple and only very important service requests are kept within the kernel call.
All other service requests are treated as external.All service requests from kernel are associated with a bounded latency
in an RTOS. This ensures highly predictable and quick response from an RTOS.
unbounded dispach latencies...more number of threads to schedule, latencies will get added up!
An RTOS has no such issues because all the process and threads in it has got bounded latencies.
RTOS is mostly a "time triggerred". where as GPOS "event triggerred"
scheduling policy : priority based(to achieve determinism) vs round-robin( to achieve high performance)
Ability to scale up / scale down as per application requirement
Less memory Management- a simple list based allocator
(no memory fragmentation solutions and virtual memomry ... like slab, slub allocator)
Generally RTOSes are meant for Low-end-Hardware configurations vs GPOS-Highend HW configurations
=======================================================================================================================
MUTEX vs SPINLOCK:
Mutex
thread tries to lock a mutex and it does not succeed, because the mutex is already locked, it will go to sleep.
immediately allowing another thread to run.
It will continue to sleep until being woken up, which will be the case once the mutex is being unlocked
by whatever thread was holding the lock before.
Disadvantage:
putting threads to sleep and waking them up again are both rather expensive operations.
they’ll need quite a lot of CPU instructions and thus also take some time.
If now the mutex was only locked for a very short amount of time, the time spent in putting a thread to sleep
and waking it up again might exceed the time the thread has actually slept by far and it might even exceed
the time the thread would have wasted by constantly polling on a spinlock
when to use Mutex??
when Lock is held for long time.
Spinlock:
When a thread tries to lock a spinlock and it does not succeed, it will continuously re-try locking it,
until it finally succeeds; thus it will not allow another thread to take its place.
(however,once the CPU runtime quantum of the current thread has been exceeded, OS will forcefully switch to another thread,).
disadvantages:
polling on a spinlock will constantly waste CPU time.
if the lock is held for a longer amount of time, this will waste a lot more CPU time and it would have been much better
if the thread was sleeping instead.
when to use spinlocaks??
Using spinlocks on a single-core/single-CPU system makes usually no sense, since as long as the spinlock polling is blocking
the only available CPU core, no other thread can run and since no other thread can run, the lock won’t be unlocked in the
quantum of time allocated for thread spinning...this merely wastes quantum of thread trying for spinlock.
On a multi-core/multi-CPU systems, with plenty of locks that are held for a very short amount of time only,
the time wasted for constantly putting threads to sleep and waking them up again might decrease runtime performance noticeably.
When using spinlocks instead, threads get the chance to take advantage of their full runtime quantum
(always only blocking for a very short time period, but then immediately continue their work), leading to much higher processing throughput.
The Practice
Since very often programmers cannot know in advance if mutexes or spinlocks will be better
(e.g. because the number of CPU cores of the target architecture is unknown), nor can operating systems know
if a certain piece of code has been optimized for single-core or multi-core environments,
most systems don’t strictly distinguish between mutexes and spinlocks.
In fact, most modern operating systems have hybrid mutexes and hybrid spinlocks.
hybrid mutexes:
behaves like a spinlock at first on a multi-core system.
Only if the lock has still not been obtained after a certain amount of time (or retries or any other measuring factor),
the thread is really put to sleep.
===============================================================================================================================
Q : Difference Binary-semaphore and Mutex ??
A:
Binary semaphores and mutexes are very similar
subtle difference:
Binary semaphores mutexes
No priority inheritance mechanism Mutexes are binary semaphores that include
a priority inheritance mechanism
Binary semaphore a better choice better choice for implementing
for implementing synchronisation simple mutual exclusion
(between tasks or between tasks and an interrupt)
semaphore can be released by any process only process acquired can relaese mutex
Q: what is Mutex purpose??
Q:what is priority inheritance ? what is it's role in Mutex objective ?
mutex : like a token to guard a resource.
When a task wishes to access the resource it must first obtain the token.
When it has finished with the resource it must 'give' the token back-allowing other tasks to access resource.
scenario: Low-pri-taskA obtains mutex, high-pri-taskC which also require same mutex, got sceduled very next moment.
Result : high-prio-taskC gets blocked for mutex and now chance that a medium-pri-taskB gets oppurtunity to run and finish.
After taskB ,Low-pri-taskA gets chance to execute and release the mutex. high-pri-taskA gets chance to execute only
after { Medium-pri-taskB Execution time + time taken by Low-pri-taskA to relese mutex} = Unbounded amount of time,untill
Medium-pri-taskB finishes.
Affect : Priority Inversion occured b/w Medim-pri-taskB and High-pri-taskC
can we mitigate the Affect?? : priority inheritance: can't avoid priority inversion but can minimise the affect
Role of priority Inheritance of Mutex - In minimising the Priority Inversion
if a high-pri-taskC blocks while attempting to obtain a mutex that is currently held by a
low-pri-taskA,then the priority of the taskA holding the mutex is temporarily raised to that of the blocking taskC
==> this doesn't allow Medim-pri-taskB to run.
Now High-pri-taskC will run as soon as Low-pri-taskB releases the Lock( still priority Inversion , but minimised effect)
Q: can we use Mutex in INTR-Handler ??
A: Interrupt handler should not block waiting for some task to release mutex(if that task obtined mutex),
so we cant use mutex in INTR-Handler
Q: Re-entrant code ??
need : single threaded process : -> single flow of control => code need not be re-entrant.
Multi threaded process : -> same Functions and resources accessed by several threads concurrently.
re-entrant Fn : protects the resource integrity.
Kernel-Memory Allocation:
32bit ===> 2^32 = 4GB virtual Memory space.
kernel is limited to 1GB-Virtual and Physical Memory
The kernel's memory is not pageable.
The kernel usually wants physically contiguous memory
Often, the kernel must allocate the memory without sleeping
void * kmalloc(size_t size, int flags); ==> allocates contiguous memory in physical memory as well as virtual memory
flag :
GFP_KERNEL => kmalloc can put current process in sleep state if memory is low.
used in process context code when it is safe to sleep.
GFP_ATOMIC:- ensures that current process is not put to sleep if memory is low.
This is the flag to use in interrupt handlers, bottom halves and other situations where you cannot sleep.
GFP_USER:- This is a normal allocation and might block. This flag is used to allocate memory for user-space processes.
kmalloc return the virtual address of first page allocated. On error it returns NULL.
when to use kmalloc:
many hardware devices cannot address virtual memory. Therefore, in order for them to be able to access a block of memory,
the block must exist as a physically contiguous chunk of memory
Another benifit: a physically contiguous block of memory can use a single large page mapping.
This minimizes the translation lookaside buffer (TLB) overhead of addressing the memory
void kfree(const void *ptr)
calling kfree() on a block of memory that already has been freed or on a pointer that is not an address returned from kmalloc()
is a bug, and it can result in memory corruption.
Calling kfree() on NULL is checked for explicitly and is safe, although it is not necessarily a sensible idea
When to use Kmalloc :-If memory required in driver needs to be contiguous in physical memory
then we have to use kmalloc for allocation of memory.
void * vmalloc(undigned long size)
vmalloc allocates contigous memory in virtual memory but it doesn't guarantee that
memory allocated in physical memory will be contiguous.
Upon successful allocation of memory it return virtual address of first byte of allocated memory block.
On failure it returns NULL.
void *vfree(const void *ptr);
When to use vmalloc?
When you are sure that memory required will never needs to interact with hardware in future
or we can if you required memory only for software purpose then we should use vmalloc for allocating memory.
If you are allocating memory that only software accesses, such as data associated with a user process, there is no need for the memory to be physically contiguous.
Kmalloc or vmalloc ??
it is often hard to find physically contiguous blocks of memory, especially for large allocations.
Allocating memory that is only virtually contiguous has a much larger chance of success.
If you do not need physically contiguous memory, use vmalloc():
Nonetheless, few allocations in the kernel use vmalloc(). Most choose to use kmalloc(), even if it's not needed,
partly for historical and partly for performance reasons.
Because the TLB overhead for physically contiguous pages is reduced greatly,
the performance gains often are well appreciated.
Despite this, if you need to allocate tens of megabytes of memory in the kernel, vmalloc() is your best option.
Kmalloc Vmalloc
1)contiguous physical memory & contiguous virtual memory 2) not guarenteed contiguous physical memory but contiguous virtual memory
2)use for memory accessible by H/w 2) for allocating buffers only for S/w usage
3) performance is high : no TLB overhead 3) TLB overhead : so dont use if high performance required while acessing mem allocated
Rules while Allocating memory in kernel:
Decide whether you can sleep (that is, whether the call to kmalloc() can block).
If you are in an interrupt handler, in a bottom half, or if you hold a lock, you cannot.
If you are in process context and do not hold a lock, you probably can.
specify GFP_KERNEL. / specify GFP_ATOMIC accordingly
If you need DMA-capable memory : specify GFP_DMA.
Always check for and handle a NULL return value from kmalloc()
Do not leak memory; make sure you call kfree() somewhere.
Ensure you never access a block of memory after you free it.
Kernel stack:
Unlike user-space processes, code executing in the kernel has neither a large nor a dynamically growing stack.
the kernel has a small fixed-size stack - 8KB typical --- 4KB for recent versions
Never allocate large memory on kernel stack.
#define BUF_LEN 2048
void rabbit_function(void)
{
char buf[BUF_LEN];
/* ... */
}
Instead, the following is preferred:
#define BUF_LEN 2048
void rabbit_function(void)
{
char *buf;
buf = kmalloc(BUF_LEN, GFP_KERNEL);
if (!buf)
/* error! */
/* ... */
}
In the kernel, however, you should use dynamic memory any time the allocation size is larger than a handful of bytes or so
Does each process have its own kernel stack ?
Not just each process - each thread has its own kernel stack (and, in fact, its own user stack as well).
Remember the only difference between processes and threads (to Linux) is the fact that multiple threads
can share an address space (forming a process).
There is just one kernel memory. In it each process has
> > it's own task_struct + kernel stack (by default 8K). There is no
special address mapping for these, nor are they allocated from a special area.
The current design is :
the kernel stack space of process and its thread_info structure are located together (at two ends because stack grows down).
This is because it offers a key benefit in terms of efficiency: the kernel can easily obtain the address of the thread_info
structure of the process currently running on a CPU from the value of its kernel ESP register.
task's kernel-stack to store a strcture thread_info
thread_info and task-struct contain pointers to each other
Switching b/w kernel stacks of processes:
When a context of some process is entered, esp is pointed to the top of
it's stack. That's exactly all it takes to exchange stacks.
The scheduler saves most of the registers onto the stack,
then it saves the stack pointer in the task_struct of the
current thread and loads the stack pointer of the next
thread to be executed. Then it simply proceeds restoring
registers from the stack.
Since registers are saved both when a thread is interrupted (or enters kernel mode through a system call or an exception)
and also by the scheduler, there will actually be two sets of registers on the stack of each thread not currently
executing on a CPU.
OK, lets say there are 20 processes running in the system. Then the
> kernel must allocate 20 * 8K = 160K just for the stacks of these
> processes. All of these 160K always occupy the kernel (kernel memory
> is never swapped out). When a process actives, ESP would switch to
> point to the corresponding stack (of that process).
Kernel-stack for Interrupts??
Depending on architecture and kernel version, the handler may
use either the kernel stack of the interrupted thread or a
special interrupt stack.
If a local variable is declared in an ISR, where it will be stored?
It will be stored in ISR stack(IRQSTACKSIZE). The ISR runs on a separate interrupt stack only if the hardware supports it.
Otherwise, the ISR stack frames are pushed onto the stack of the interrupted thread.
As interrupts comes per cpu, therefore ISR stack has to be per cpu.
System calls:
glibc provides wrapper code which abstracts you away from the underlying code which arranges the arguments
you’ve passed and enters the kernel.
The Linux kernel includes a file which lists each system call in a table.
There are a few scripts which run at compile time which take syscalltable and generate the syscalls_32.h file from it.
this header file provides syscall numbers, which can be used by user programs.
The Linux kernel sets aside a specific software interrupt number that can be used by user space programs to enter the kernel
The Linux kernel registers an interrupt handler for the interrupt number: 128 (0x80)
The userland program is expected to put the system call number in the eax register.
The arguments for the syscall itself are to be placed in the remaining general purpose registers.
system-call service Function indexed using syscall number in system call table.
return address and the CPU flags which encode the privilege level (and other stuff),
and more are all saved on the program stack before ia32_syscall executes.
After system call service is Executed..kernel just needs to copy these values from the program stack back into the registers where they belong and execution will resume back in userland.
this is done using iret instruction.
Fast system calls:
SCHEDULER:
From a high level, the scheduler is simply a grouping of functions that operate on given data structures.--kernel/sched.c
A task/process/thread in the scheduler is a collection of data structures and flow of control.
a process has been initialized and placed on a run queue:
struct task_struct *p;
p->state = TASK_RUNNING ; =====> task is in run-Q
In Linux, the run queue is composed of two priority arrays:
Active.: Stores processes that have not yet used up their timeslice
Expired.: Stores processes that have used up their timeslice.
From a high level, the scheduler’s job in Linux is to take the highest priority active processes,
let them use the CPU to execute,
and place them in the expired array when they use up their timeslice.
at some time, this process should have access to the CPU to execute.
The two functions that are responsible for passing CPU control to different processes are schedule() and scheduler_tick()
schedule(): how the Linux kernel decides which process to execute next
scheduler_tick() : how the kernel determines which processes need to yield the CPU ??
When a timer event occurs,the current process is put on hold and the Linux kernel itself takes control of the CPU.
scheduler_tick() is a system timer isr that the kernel calls and marks processes as needing rescheduling / not needed.
When the timer event finishes, the Linux kernel normally passes control back to the process that was put on hold. However, when the held process has been marked as needing rescheduling, the kernel calls schedule() to choose which process to activate instead of the process that was executing before the kernel took control. The process that was executing before the kernel took control is called the current process.
Example:
Process A has control of the CPU and is executing.
The system timer scheduler_tick() goes off, takes control of the CPU from A, and marks A as needing rescheduling.
The Linux kernel calls schedule(), which chooses Process B and the control of the CPU is given to B.
Process B executes for a while and then voluntarily yields the CPU.
This commonly occurs when a process waits on some resource.
B calls schedule(), which chooses Process C to execute next.
Process C executes until scheduler_tick() occurs, which does not mark C as needing rescheduling.
This results in schedule() not being called and C regains control of the CPU.
The system timer scheduler_tick() goes off, takes control of the CPU from C, and marks C as needing rescheduling
The Linux kernel calls schedule(), which chooses Process A and the control of the CPU is given to A.
PROCESS states in KERNEL-SPACE:
----------------------------------
p->state = RUNNING : run-Q => ready state==> ready to run in run-Q / running on cpu.
yielding the processor:
calling schedule();==> when p->state = RUNNING,merely moves process to run-Q. this will execute again when scheduler chooses it.
sceduler can't choose to execute a process , if it is sleeping / waiting on an Event.:
SLEEPING in the kernel:
If procesing is waiting on some resource , It blocks by calling schedule().
set_current_state(TASK_INTERRUPTIBLE); or set_current_state(TASK_UNINTERRUPTIBLE);
schedule();
Interruptible and Uninterruptible Sleep:
Interruptible sleep is the preferred way of sleeping, unless there is a situation in which signals cannot be handled at all,
such as device I/O.
p-> state = TASK_INTERRUPTIBLE : can be wokenup by signals or by calling wake_up_process()from other process kernel code.
p-> state = TASK_UNINTERRUPTIBLE : can be wokenup only by wake_up_process() call.
########################################################################################################
SLEEPING in Kernel -Detailed Explaination:
--------------------------------------------
Typical scenario for blocking on event / sleeping:
A --> processing list items
B---> adding items to list
To access-List : lock should be aquired.
Process A:
1 spin_lock(&list_lock);
2 if(list_empty(&list_head)) {
3 spin_unlock(&list_lock);
4 set_current_state(TASK_INTERRUPTIBLE);
5 schedule();
6 spin_lock(&list_lock);
7 }
8
9 /* Rest of the code ... */
10 spin_unlock(&list_lock);
==========>
LOCK , listEmpty-Check
if Empty - UNLOCK , SLEEP .
when wokeup by B --> LOCK , process list-item , UNLOCK
here A-> is marked as RUNNING by wake_up_process call of B.
and is sceduled only when A get it's time slot allocated.
Process B:
100 spin_lock(&list_lock);
101 list_add_tail(&list_head, new_node);
102 spin_unlock(&list_lock);
103 wake_up_process(processa_task);
LOCK ,Add-listItem, UNLOCK, WAKEUP--> marks A - RUNNING., do other work, time-slotExp-> relinquish CPU
Lost Wake-Up Problem:
------------------------
Race condition: After LOCK and before SLEEP if processA time-slot Expires.
process-B scheduled , adds item, and Wakesup-A, does some other work,after-timeslot expires moved out of cpu.
process-A sceduled , SLEEPs...woken up only when B-sceduled Again,Adds-listItem,wakes-up A.
processA --lost one Wakeup : common problem in kernel.
solution:
-----------
Process A:
1 set_current_state(TASK_INTERRUPTIBLE);
2 spin_lock(&list_lock);
3 if(list_empty(&list_head)) {
4 spin_unlock(&list_lock);
5 schedule();
6 spin_lock(&list_lock);
7 }
8 set_current_state(TASK_RUNNING);
9
10 /* Rest of the code ... */
11 spin_unlock(&list_lock);
====>
A----> MARK as INTERRUPTIBLE , LOCK, check-ListEmpty, if Empty UNLOCK and yield CPU.
if processA -loses cpu before schedule()---> process B adds Item and wake_up_process(A)- marks A as RUNNING.,
B executed for some time and loses cpu.
now when A runs -->schedule() call merily causes A to yields CPU.==> A wont sleep.
hence..when A gets chance to run based on scheduler Algorithm , it processes ListItem.
Example code in kernel:
4253 /* Wait for kthread_stop */
4254 set_current_state(TASK_INTERRUPTIBLE);
4255 while (!kthread_should_stop()) {
4256 schedule();
4257 set_current_state(TASK_INTERRUPTIBLE);
4258 }
4259 __set_current_state(TASK_RUNNING);
4260 return 0;
The thread cannot exit until the kthread_should_stop() function return TRUE.
while waiting for the function to return 0 : thread sleeps
it keeps checking untill the Fn to return TRUE.,,whenever woken-up by other thread.
kthread_should_stop condition is made only after the state is TASK_INTERRUPTIBLE.
Hence, the wake-up received after the condition check but before the call to schedule() function is not lost.
Wait queues: higher-level mechanism used to put processes to sleep and wake them up
wait_queue_head_t my_event;
init_waitqueue_head(&my_event);
(or)
DECLARE_WAIT_QUEUE_HEAD(my_event);
Wait:
wait_event(&my_event, (event_present == 1) ); --------------------> UNINTERRUPTIBLE sleep
wait_event_interruptible(&my_event, (event_present == 1) ); ---------> INTERRUPTIBLE sleep
Wakeup:
wake_up(&my_event);: wakes up only one process from the wait queue.
wake_up_all(&my_event);: wakes up all the processes on the wait queue.
wake_up_interruptible(&my_event);: wakes up only one process from the wait queue that is in interruptible sleep.
Example:
291 static int smbiod(void *unused)
292 {
299 for (;;) {
300 struct smb_sb_info *server;
301 struct list_head *pos, *n;
302
303 /* FIXME: Use poll? */
304 wait_event_interruptible(smbiod_wait,
305 test_bit(SMBIOD_DATA_READY,
&smbiod_flags));
...
... /* Some processing */
312
313 clear_bit(SMBIOD_DATA_READY,
&smbiod_flags);
... /* Code to perform the requested I/O */
...
...
337 }
338
339 VERBOSE("SMB Kernel thread exiting (%d)...\n", current->pid);
340 module_put_and_exit(0);
341 }
342
Thundering Herd Problem: arises due to the use of the wake_up_all function
scenario : set of processes are sleeping on a wait queue, wanting to acquire a lock.
Once the process that has acquired the lock is done with it,
it releases the lock and wakes up all the processes sleeping on the wait queue.
All the processes try to grab the lock. Eventually, only one of these acquires the lock
and the rest go back to sleep.
penalty : It consumes valuable CPU cycles and incurs context-switching overheads
Q: If we already know that only one process is going to resume while the rest of the processes go back to sleep again,
why wake them up in the first place?
A: wake_up_all function should be done carefully, only when you know that it is required.
Otherwise, go ahead and use the wake_up function that wakes up only one process at a time.
Q: when would the wake_up_all function be used?
A: It is used in scenarios when processes want to take a shared lock on something.
For example, processes waiting to read data on a page could all be woken up at the same moment.
Time-Bound Sleep":
schedule_timeout(timeout), a variant of schedule(): puts the process to sleep until timeout jiffies have elapsed.
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(20msec);
If execution of your process to be delayed for a given amount of time.
It may be required to allow the hardware to catch up
or to carry out an activity after specified time intervals, such as polling a device, flushing data to disk or retransmitting a network request.
1415 set_current_state(TASK_INTERRUPTIBLE);
1416 for (;;) {
1417 schedule_timeout(APM_CHECK_TIMEOUT);
1418 if (exit_kapmd)
1419 break;
1421 * Ok, check all events, check for idle
.... * (and mark us sleeping so as not to
.... * count towards the load average)..
1423 */
1424 set_current_state(TASK_INTERRUPTIBLE);
1425 apm_event_handler();
1426 }
You also may use a more convenient API, with which you can specify time in milliseconds and seconds:
msleep(time_in_msec);
msleep_interruptible(time_in_msec);
ssleep(time_in_sec);
These higher-level routines internally convert the time into jiffies,
appropriately change the state of the process and call schedule_timeout(), thus making the process sleep.
PREEMPTION in linux:
Until kernel version 2.4, only user processes were preemptive,
i.e., in addition to time quantum expiration, an execution of current process in user mode would be interrupted
if higher dynamic priority processes entered TASK_RUNNING state .
Toward 2.6 series of the Linux kernel, an ability to interrupt a task executing kernel code was added,
although with that not all sections of the kernel code can be preempted.
The Linux kernel provides preemptive scheduling under certain conditions.
Through the use of the real-time Linux kernel patch PREEMPT_RT,
support for full preemption of critical sections, interrupt handlers, and "interrupt disable" code sequences can be supported.
Preemption improves latency, increases responsiveness, and makes Linux more suitable for desktop and real-time applications
Critical sections protected by semaphores can be preempted at any time,
as every contention will end in a schedule and there can't be any deadlock.
However, critical sections protected by fast spin locks or by hand locks cannot be preempted unless we block the timer IRQ.
So all spin locks should be IRQ-safe.
Preemption
----------------
On UP, spin_lock is defined as preempt_disable, and spin_unlock is defined as preempt_enable.
On SMP, they also perform locking.
The nestable preemption markers(preempt_disable and preempt_enable) operate on preempt_count,
a new integer stored in each task_struct.
preempt_disable effectively is:
++current->preempt_count;
barrier();
and preempt_enable is:
--current->preempt_count;
barrier();
if (unlikey(!current->preempt_count
&& current->need_resched))
preempt_schedule();
The result is we do not preempt when the count is greater than zero.
preempt_schedule: It sets a flag in the current process to signify it was preempted,
calls schedule and, upon return, unsets the flag:
asmlinkage void preempt_schedule(void)
{
do {
current->preempt_count += PREEMPT_ACTIVE;
schedule();
current->preempt_count -= PREEMPT_ACTIVE;
} while (current->need_resched);
}
Implicitly locked data vs pre-emption
-----------------------------------------
In non-preemptive kernel:
per-CPU data (data structures unique to each CPU) do not require locking.
such data is protected by its nature, is considered to be “implicitly locked”.
a task on another CPU cannot mangle the first CPU's data.
But Implicitly locked data and preemption do not get along.
With preemption, a process on the same CPU can find itself preempted,
and a second process can then trample on the data of the first.
such data can be protected by the existing SMP locks.
is there any other way to protect such a data without using SMP-locks??
The solution, thankfully, is simple: disable preemption around access to the data.
For example:
int catface[NR_CPUS];
preempt_disable();
catface[smp_processor_id()] = 1; /* index catface
by CPU number */
/* operate on catface */
preempt_enable();
Uniprocessor Kernel VS SMP-symmetric Multi-processor Kernel
If more than one process can access data at the same time,
mutual exclusion must be introduced to protect this shared data.
race conditions occur when two processes access the same data structure in memory.
causing inconsistent states of data
if such critical sections are short,
Short-term mutual exclusion :
On UP systems: this could only occur if one process is preempted by the other.
To protect critical sections, they are guarded with some sort of preempt_disable/ preempt_enable call to disable preemption,
so a process can finish the critical section without being interrupted by another process.
In a non- preemptive kernel, no measures have to be taken at all
In SMP-system : disabling pre-emption won't help as process2 may get sceduled in other cpu.
interrupt handlers that access shared data
interrupt handler code from interrupting a process in a critical section
short-term mutual exclusion with interrupts:
To prevent ,guard a critical section in the process context with some sort of cli/sti(disable/ enable all interrupts) call.
SMP systems as well, because all other CPUs' interrupts are still active and can execute the interrupt handler code at any time.
processes being held up accessing a shared resource for a longer time.
For example, once a write system call to a regular file begins, it is guaranteed by the operating system that any other read or write system calls to the same file will be held until the current one completes
long-term mutual exclusion:
A write system call may require one or more disk I/O operations in order to complete the system call. Disk I/O operations are relatively long operations when compared to the amount of work that the CPU can accomplish during that time.
It would therefore be highly undesirable to inhibit preemption for such long operations, because the CPU would sit idle waiting for the I/O to complete
semaphores are used to solve this problem. This also holds true for SMP systems.
what is MutualExclusion :?
mutual exclusion :
we want changes to appear as if they were an atomic operation.
If we can not update data with an atomic operation,
we need to make an update uninterruptible and sequentialize it with all other processes
that could access the data
Most SMP architectures possess some operations that read and change data within a single, uninterruptible step, called atomic operations.
test and set (TSR)
compare and swap (CAS),
load link/ store conditional instruction pair (LL/SC).
atomic_t counter = ATOMIC_INIT(0); atomic_inc(&counter); ==.> use such instructions
Spin Locks:
A flag variable indicates if a process is currently in the critical section (lock_var = 1) or if no process is in the critical section (lock_var = 0)
A process spins (busy waits) until the lock is reset, then sets the lock.
Testing and setting of the lock status flag must be done in one step, with an atomic operation. To release the lock, a process resets the lock variable
spin lock can not be acquired recursively ===> during second call : process spins disabling pre-emption.
this has two implications: process cant be pre-empted till that time slot expires.
as the same process holds lock,no way to unlock,==>deadlock. All the time slots allocated
for that process are wasted in spinning and process never finished.
process acquired spinlock , ISR interrupts that process , try to acquire it ===> deadlock.
if code-spins in ISR: pre-emption disabled and INTRs disabled , so no way any other process being scheduled .
===> kernel panic
SMP case : pr1 holds spinlock , ISR on cpu2 spins : not a problem because pr1 releses after some time.
pr1 holds spinlock , ISR1 on cpu1 spins ,ISR2 on cpu2 also spins ==> deadlock
so spin locks can not be used within interrupt handlers:
A critical section in process context is guarded by spin_lock_irq() :
irq-safe versions of spin_lock disable all interrupts on the local CPU for the critical section:
The possibility of a deadlock is therefore eliminated.
critical sections in interrupt handlers are guarded by the normal spin_lock().
UP : If process1 holds spinlock, ISR is stalled.
once process1 releases spinlock, ISR runs .
SMP:
While CPU 1 (in process context) is holding the lock, any incoming interrupt requests on CPU 1 are stalled until the lock is released. An interrupt on CPU 2 busy waits on the lock, but it does not deadlock. After CPU 1 releases the lock, CPU 2 (in interrupt context) obtains it, and CPU 1 (now executing the interrupt handler) waits for CPU 2 to release it.
Sometimes it is wanted to allow spin locks to be nested.
a nesting counter and a variable indicating which CPU holds the lock
the lock is held, the lock function checks if the current CPU is the one holding the lock. In this case, the nesting counter is incremented and it exits from the spin loop.
The unlock function decrements the nesting counter. The lock is released when the unlock function has been called the same number of times the lock function was called before. This kind of spin lock is called a recursive lock.
adaptive locks.:
When one thread attempts to acquire one of these that is held by another thread, it checks to see if the second thread is active on a processor. If it is, the first thread spins. If the second thread is blocked, the first thread blocks as well.
Using mutexes instead of spin locks is productive, if the critical section takes longer than a context switch. Else, the overhead of blocking compared to busy waiting for a lock to be released is worse. On the pro side, mutexes imply a kind of fairness, while processes could starve on heavily contended spin locks. Mutexes can not be used in interrupts, because it is generally not allowed to block in interrupt context.
Mutex:/Binary-semaphore:
semaphores (initialized with a counter value of 1) can also be used for protecting critical sections. For performance reasons, semaphores used for this kind of work are often realized as a separate primitive, called mutex, that replaces the counter with a simple lock status flag.
surprise !!!!!!
A semaphore is a complex shared data structure itself, and must therefore be protected by an own spin lock.
##########################################################################################################
http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/
Anatomy of a Program:
KERNEL SPACE : 1GB -- 0xFFFF|FFFF to ox0000|0000
==> doesn't mean that this much of memory available , that portion of address space available
to map to available physical memory .
In Linux, kernel space is constantly present and maps the same physical memory in all processes.
Kernel code and data are always addressable, ready to handle interrupts or system calls at any time.
USER SPACE : 3GB ---
For a given process:
some virtual addresses are mapped to physical memory. others are not.
The mapped distinct bands in the address space correspond to memory segments like the heap, stack,...etc.
starting virtual addresses for the segments are randomised by adding offsets to their starting addresses.--For security.
if they are same for all the processes, as in olden days-- security exploitation.
user-space of process changes whenever a process switch happens:
For each process there is distinct bands of address segments which map to different Physical pages.
Linux processes are implemented in the kernel as instances of task_struct, the process descriptor.
The mm field in task_struct points to the memory descriptor, mm_struct,: summary of a program’s memory.
= the set of virtual memory areas and the page tables.
struct vm_area_struct
Each virtual memory area (VMA) is a contiguous range of virtual addresses
vm_start --> first address within VMA.
vm_end ----> first address after VMA.
flags ----> Access permissions of Area.
vm_file ----> file is being mapped by the area, if any ( A VMA that does not map a file is anonymous.)
VMAs do not care which segment they are in.
Assignment:
A program’s VMAs are stored in its memory descriptor both as a linked list in the mmap field,
ordered by starting virtual address, and as a red-black tree rooted at the mm_rb field.
The red-black tree allows the kernel to search quickly for the memory area covering a given virtual address.
When you read file /proc/pid_of_process/maps, the kernel is simply going through the linked list of VMAs
for the process and printing each one.
: read the VMAs of currently(?)running process and display in proc.
The 4GB virtual address space is divided into pages of size 4KB.
The size of a VMA must be a multiple of page size.
The processor consults page tables to translate a virtual address into a physical memory address.
To each virtual page there corresponds one page table entry (PTE) in the page tables,
Each process has its own set of page tables;
whenever a process switch occurs, page tables for user space are switched as well.
typical page size = 4KB ---> 12-bit offset field in address
virtual-pages --> 20 bits : 1G pages ==> 1MB PTEs
each PTE 4 bytes ==> 4MB memory (1k-pages per process)
If 100-processes==> 400MB (100-pages per system)
we cant keep some of them in Disk ==> because
if pageFrame not in mem : we can get it from disk via PTE.
if PTE is not in memory : no way to get it from Disk.
solution : Another level of indirection : level-1-page table Always in memory , level-2 resides in disk.
only first-level table needs to be reside always in memory
Linux case : 3-level-page-table PGD(page global directory)---> PMD(page Middle directory)--> PTE
calculation:
Linux maintains the concept of a three-level page table in the architecture independent code
even if the underlying architecture does not support it.
Architectures that manage their Memory Management Unit (MMU) differently are expected to emulate the three-level page tables.
For example, on the x86 without PAE enabled, only two page table levels are available.
(PMD) is defined to be of size 1 and “folds back” directly onto the (PGD) which is optimised out at compile time.
during context switch:
pointer to the current page tables is swapped
Up to version 2.6.10, the Linux paging model consisted of three paging levels.
Starting with version 2.6.11, a four-level paging mode
PGD : page global directory
PUD : page upper directory
PMD : page middle directory
PTE : page table entry
assignment: Walk through ing page tables of a process in Linux ??
ptr-to-(struct mm_struct) -->pgd_t * pgd ===> pointer to a process’ page tables.
?? which page-table?? where it resides?? level-1-page-table?? is level-1-page table common for all processes??
PTE : Base-addr of physical-page , P,
P ->present:
R/W
U/S
D -> Dirty : page had a write --sticky-flag (i.e set by cpu ,cleared by kernel)
A -> Accessed : page accessed Read or Write-- sticky-flag (i.e set by cpu ,cleared by kernel)
Physical memory is managed with the buddy memory allocation technique:
struct page { :
Each physical page in the system has a struct page associated with it to keep track of
whatever it is we are using the page for at the moment
Each segment in program area represented by vm_area_struct
vm_area_struct : has several virtual pages which are mapped to Physical page frames via Page tables
tasks in linux:
To the Linux kernel, there is no concept of a thread. Linux implements all threads as standard tasks.
The Linux kernel does not provide any special scheduling semantics or data structures to represent threads.
Instead, a thread is merely a process that shares certain resources with other processes.
In Windows or Sun Solaris have explicit kernel support for threads.
Threads are created like normal tasks, with the exception that the clone() system call is passed flags corresponding to specific resources to be shared:
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0); //thread created with these resources shared with parent.
normal fork() ==> implemented as clone(SIGCHLD, 0); // regular process created.
vfork() is implemented as clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0); // create a child process and block parent
vfork() differs from fork in that the calling thread is suspended until the child terminates
(either normally, by calling _exit, or abnormally, after delivery of a fatal signal),or it makes a call to execve.
It should not call exit() -- Why ???
Until that point, the child shares all memory with its parent,including the stack.
As with fork,the child process created by vfork() inherits copies of various of the caller's process attributes
(e.g., file descriptors, signal dispositions, and current working directory);
the vfork() call differs only in the treatment of the virtual address space
Why- vfork ??
Under Linux, fork(2) is implemented using copy-on-write pages, so the only penalty incurred by fork(2)
is the time and memory required to duplicate the parent's page tables,and to create a unique task structure for the child.
However, in the bad old days a fork(2) would require making a complete copy of the caller's data space,
often needlessly, since usually immediately afterward an exec(3) is done.
Thus, for greater efficiency, BSD introduced the vfork() system call, which did not fully copy the address space
of the parent process, but borrowed the parent's memory and thread of control until a call to execve(2) or an exit occurred.
The parent process was suspended while the child was using its resources.
The use of vfork() was tricky: for example, not modifying data in the parent process depended on knowing
which variables were held in a register.
Analysis:
Initially vfork(): when there was no copy on write.==> to avoid duplication of Entire parent process memory
Kernel thread(kernel-spae-process) VS user-space process:
kernel threads: standard processes that exist solely in kernel-space., schedulable and preemptable like user-processes
kernel threads do not have an address space (in fact, their mm pointer is NULL).
a kernel thread can be created only by another kernel thread.via interface : int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
(unsigned long)arg, NULL, NULL);
}
created via the usual clone() system call
kthread function usually implements a loop in which the kernel thread wakes up as needed, performs its duties, and then returns to sleep.
kthread_create/kthread_run : in kernel/kthread.c
task_struct :
task_struct structure is allocated via the slab allocator to provide object reuse and cache coloring