Multi-tasking
• Modern operating systems allow multiple
users to share a computer’s resources
• Users are allowed to run multiple tasks
• The OS kernel must protect each task
from interference by other tasks, while
allowing every task to take its turn using
some of the processor’s available time
Stacks and task-descriptors
• To manage multitasking, the OS needs to
use a data-structure which can keep track
of every task’s progress and usage of the
computer’s available resources (physical
memory, open files, pending signals, etc.)
• Such a data-structure is called a ‘process
descriptor’ – every active task needs one
• Every task needs its own ‘private’ stack
29 trang |
Chia sẻ: candy98 | Lượt xem: 946 | Lượt tải: 0
Bạn đang xem trước 20 trang tài liệu Advanced Systems Programming - Lesson 5: The kernel’s task list, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
The kernel’s task list
Introduction to process descriptors
and their related data-structures
for Linux kernel version 2.6.22
Multi-tasking
• Modern operating systems allow multiple
users to share a computer’s resources
• Users are allowed to run multiple tasks
• The OS kernel must protect each task
from interference by other tasks, while
allowing every task to take its turn using
some of the processor’s available time
Stacks and task-descriptors
• To manage multitasking, the OS needs to
use a data-structure which can keep track
of every task’s progress and usage of the
computer’s available resources (physical
memory, open files, pending signals, etc.)
• Such a data-structure is called a ‘process
descriptor’ – every active task needs one
• Every task needs its own ‘private’ stack
What’s on a program’s stack?
Upon entering ‘main()’:
• A program’s exit-address is on user stack
• Command-line arguments on user stack
• Environment variables are on user stack
During execution of ‘main()’:
• Function parameters and return-addresses
• Storage locations for ‘automatic’ variables
Entering the kernel
A user process enters ‘kernel-mode’:
• when it decides to execute a system-call
• when it is ‘interrupted’ (e.g. by the timer)
• when ‘exceptions’ occur (e.g. divide by 0)
Switching to a different stack
• Entering kernel-mode involves not only a
‘privilege-level transition’ (from level 3 to
level 0), but also a stack-area ‘switch’
• This is necessary for robustness:
e.g., user-mode stack might be exhausted
• This is desirable for security:
e.g, privileged data might be accessible
What’s on the kernel stack?
Upon entering kernel-mode:
• task’s registers are saved on kernel stack
(e.g., address of task’s user-mode stack)
During execution of kernel functions:
• Function parameters and return-addresses
• Storage locations for ‘automatic’ variables
Supporting structures
• So every task, in addition to having its own
code and data, will also have a stack-area
that is located in user-space, plus another
stack-area that is located in kernel-space
• Each task also has a process-descriptor
which is accessible only in kernel-space
A task’s virtual-memory layout
User space
Kernel space
User-mode stack-area
Task’s code and data
Privilege-level 0
Privilege-level 3
process descriptor
and
kernel-mode stack
Shared runtime-libraries
The Linux process descriptor
task_struct
state
*stack
flags
*mm
exit_code
*user
pid
*files
*parent
mm_struct
*pgd
pagedir[]
user_struct
signal_struct
*signal
files_struct
Each process
descriptor
contains many
fields
and some are
pointers to
other kernel
structures
which may
themselves
include fields
that point to
structures
Something new in 2.6
• Linux uses part of a task’s kernel-stack
page-frame to store ‘thread information’
• The thread-info includes a pointer to the
task’s process-descriptor data-structure
Task’s kernel-stack
Task’s thread-info
page-frame aligned
Task’s
process-descriptor
struct task_struct 8-KB
Tasks have ’states’
From kernel-header:
• #define TASK_RUNNING 0
• #define TASK_INTERRUPTIBLE 1
• #define TASK_UNINTERRUPTIBLE 2
• #define TASK_STOPPED 4
• #define TASK_TRACED 8
• #define TASK_NONINTERACTIVE 64
• #define TASK_DEAD 128
Fields in a process-descriptor
struct task_struct {
volatile long state;
void *stack;
unsigned long flags;
struct mm_struct *mm;
struct thread_struct *thread;
pid_t pid;
char comm[16];
/* plus many other fields */
};
Finding a task’s ‘thread-info’
• During a task’s execution in kernel-mode, it’s
very quick to find that task’s thread-info object
• Just use two assembly-language instructions:
movl $0xFFFFF000, %eax
andl %esp, %eax
Ok, now %eax = the thread-info’s base-address
There’s a macro that implements this computation
Finding task-related kernel-data
• Use a macro ‘task_thread_info( task )’ to
get a pointer to the ‘thread_info’ structure:
struct thread_info *info = task_thread_info( task );
• Then one more step gets you back to the
address of the task’s process-descriptor:
struct task_struct *task = info->task;
The kernel’s ‘task-list’
• Kernel keeps a list of process descriptors
• A ‘doubly-linked’ circular list is used
• The ‘init_task’ serves as a fixed header
• Other tasks inserted/deleted dynamically
• Tasks have forward & backward pointers,
implemented as fields in the ‘tasks’ field
• To go forward: task = next_task( task );
• To go backward: task = prev_task( task );
Doubly-linked circular list
init_task
(pid=0)
newest
task
next_task
prev_task
Demo
• We can write a module that lets us create
a pseudo-file (named ‘/proc/tasklist’) for
viewing the list of all currently active tasks
• Our ‘tasklist.c’ module shows the name
and process-ID of each task, along with
that task’s current ‘state’ (0, 1, 2, 4, 8,)
• Use the command: $ cat /proc/tasklist to
display a complete list of the active tasks
Maybe a big /proc file
• We can’t know ahead of time how many
tasks are active in our system – this will
depend on many varying factors, such as
who else is logged in, which commands
have been issued, whether we’re using
text-mode console or graphical desktop
• So it’s perfectly possible our pseudo-file
might ‘overflow’ its kernel-supplied buffer!
How to avoid buffer-overflow
• Our module’s ‘get_info()’ callback-function has
four parameter-values passed to it by the kernel:
• char *buf - address of a small kernel buffer
• char **start - address of a pointer variable
• off_t offset - current offset of file-pointer
• int buflen - size of the kernel buffer
• The initial conditions are:
offset == 0 and *start == NULL
• Kernel’s behavior will vary if we modify *start
Normal case
• We expect the ‘/proc’ file to deliver a small
amount of text-data (not more than would
fit in the kernel-supplied buffer (e.g., 3KB)
• So we make no change to ‘*start’
• Then kernel will deliver the data it finds in
the buffer it had supplied to ‘get_info()’
• The kernel will not call ‘get_info()’ again
(unless our file is closed and reopened)
Alternative case
• Our ‘get_info()’ function modifies the value
of the (initially NULL) ‘*start’ pointer – for
example, maybe assigning it the address
of some buffer we’ve allocated, or even
assigning the address of the kernel-buffer:
*start = buf;
• In this case, the kernel will again call our
module’s ‘get_info()’ function, provided we
returned a nonzero function-value before!
The benefit
• Knowing about this alternative option, we
can design our ‘get_info()’ function so that
it delivers a big amount of data in several
small-size chunks, never overflowing the
size-limitations on the kernel’s buffer
• We just need to think carefully about the
differing senarios under which ‘get_info()’
will be repeatedly called
First pass
• The value of ‘offset’ will be zero
• We set *start to a buffer-address where
we place a positive number of data-bytes
• Kernel delivers those bytes to the ‘reader’,
taking them from the *start address, then
advances the file-pointer by that amount
• Kernel calls our ‘get_info()’ again, but with
a non-zero ‘offset’ value this time!
Final time
• When our ‘get_info()’ function has finally
finished delivering all the desired data to
the file’s ‘reader’, and still we receive yet
another ‘get_info()’ call, then we simply
return a function-value equal to zero,
telling the kernel that the data has been
exhausted -- and so not to call again!
Our implementation
struct task_struct *task; // ‘global’ variables’ values remembered
int my_get_info( char *buf, char **start, off_t offset, int buflen )
{
int len = 0;
if ( offset == 0 ) // our first time through this function
{
task = &init_task; // start of circular linked-list
}
else if ( task == &init_task ) return 0; // our final pass
// put some data into the kernel-supplied buffer
len += sprintf( buf+len, “pid=%d \n”, task->pid );
*start = buf; // tell kernel where to find data, and to call again
task = next_task( task ); // advance to next node of circular list
return len; // and tell kernel how far to advance
}
In-class exercise #1
• Different versions of the 2.6 Linux kernel use
slightly different definitions for the task-related
kernel data-structures (e.g., the 2.6.10 kernel
used a smaller-sized ‘thread-info’ structure than
2.6.9 kernel did)
• So, by using the C ‘sizeof’ operator, can you
quickly create an LKM that will show us:
– the size of a ‘task_struct’ object (in bytes)?
– the size of a ‘thread_info’ object (in bytes)?
‘Kernel threads’
• Some tasks don’t have a page-directory of
their own – because they don’t need one
• They only execute code, and access data,
that resides in the kernel’s address space
• They can just ‘borrow’ the page-directory
that belongs to another task
• These ‘kernel thread’ tasks will store the
NULL-pointer value (i.e., zero) in the ‘mm’
field of their ‘task_struct’ descriptor
In-class exercise #2
• Can you modify our ‘tasklist.c’ module so it
will display a list of only those tasks which
are ‘kernel threads’? (i.e., task->mm == 0)
• How many ‘kernel threads’ on your list?