LINUX KERNEL SERIES
Author:Dinesh Ahuja
Title:Linux Kernel Series: Linux Kernel Threads
The purpose of this article is to cover Linux implementation of kernel threads and how
these kernel threads can be used in kernel modules. This article will show how a kernel
thread can be put to use for a monitoring purpose.
Most of today's operating systems provide multi-threading support and linux is not different from these operating systems. Linux support threads as they provide concurrency or parallelism on multiple processor systems. Most of the operating systems like Microsoft Windows or Sun Solaris differentiate between a process and a thread i.e. they have an explicit support for threads which means different data structures are used in the kernel to represent a thread and a process.
Linux implementation of threads is totally different as compared to the above-mentioned operating systems. Linux implements threads as a process that shares resources among themselves. Linux does not have a separate data structure to represent a thread. Each thread is represented with task_struct and the scheduling of these is the same as that of a process. It means the scheduler does not differentiate between a thread and a process.
KERNEL THREADS
Kernels can attain parallelism by spawning some operations in the background. The kernel achieves this by delegating these operations to the kernel threads. A kernel thread is a process that exists only in kernel space and does not have access to user address space i.e. mm pointer is NULL. Being an integral part of a kernel and running in a kernel address space, they have an access to kernel data structures. A Kernel thread does not have a privilege over other processes and hence they are also as schedulable and pre-emptable as any other process.
The kernel symbol table contains the addresses of global kernel items – functions and
variables that can be accessed by modules. The symbols exported by statically linked kernels
and all linked-in modules can be retrieved by reading the /proc/ksyms file. Even kernel
modules can export their respective symbols so that they can be accessed by other kernel
modules.
The interface to create a kernel thread is as below:
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
This is defined in arch/i386/kernel/process.c. In this, a new process executes a function specified by fn argument and arg is passed as an argument to this function.
kernel_thread() function is exported by a kernel and hence it can be accessed from kernel modules. To export a symbol from kernel or a module, use EXPORT_SYMBOL(symbol) macro.This macro causes symbol to be added into a kernel symbol table i.e. __ksymtab section of kernel code.
IMPLEMENTATION DETAILS
The implementation of kernel_thread() function is available in arch/i386/kernel/process.c. The new task is created via do_fork() system call. A kernel_thread() system call is invoked with a special flag CLONE_KERNEL as an argument. This is defined in include/linux/sched.h as below in
define CLONE_KERNEL (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)
Internally, kernel_thread() passes another two flags CLONE_VM and CLONE_UNTRACED to
do_fork() system call. It means a newly created process will share following resources with its parent process.
File System Information.
File Descriptor Table.
Signal Handlers.
Memory space.
A do_fork() is responsible to create a new task structure on the basis of the
flags passed to it, to set a state of newly created task to TASK_RUNNING and put
this task structure on a runqueue list. It depends upon scheduler when this task
is being picked up for execution.
SAMPLE CODE
A sample code will create a kernel thread whose job is to iterate over a tasklist
associated with a CPU and prints all its details using printk() function. A kernel thread
will be responsible to see whether a process is a zombie process. A Zombie process blocks
the memory in a process table and this memory should be released as it blocks resources.
A schedule_timeout() function makes a current task sleep until timeout jiffies have
expired. On return, the task is guaranteed to be in a TASK_RUNNING state. A following code
will cause a current task to get removed from runqueue.
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(30*HZ);
A schedule_timeout() will be internally invoke schedule() which will cause a current task
to be removed from a runqueue, if there is no pending signals for the current process.
Each call to schedule_timeout will cause timer_list to get added into a global list of
timers on FIFO basis.
struct timer_list {
struct list_head list;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long);
};
For every timer_list, the function pointer is set to an address of process_timeout()
whose implementation is given below:
static void process_timeout(unsigned long __data)
{
wake_up_process((task_t *)__data);
}
The purpose of this function is to put a task back into a runqueue and set its state
to TASK_RUNNING before returning. The TIMER_BH bottom half is responsible to invoke a
run_timer_list() function which internally invokes process_timeout() with timer_list->
data as an argument.
Note: Invoking schedule_timeout() without setting task state to TASK_INTERRUPTIBLE would
not cause it to get removed from a runqueue and therefore will have no impact on process's
scheduling.
To spawn a kernel thread, we need to define a thread function which is defined as below:
static int print_taskinfo(void * data)
{
printk("<1>::Inside Thread Function::\n");
printk("Kernel Thread::Current Process is \"%s \" (pid: %i) \n",
current->comm,current->pid);
sprintf(current->comm,"MyTask" );
set_current_state(TASK_INTERRUPTIBLE);
void * sZombie = kmalloc(10 * sizeof(char), GFP_KERNEL);
memset(sZombie,'\0', 10 * sizeof(char));
// Not very clear who will clean up a temporary memory allocated for
// string literals "ZOMBIE" & "NON-ZOMBIE"...
memcpy(sZombie, "ZOMBIE", 7);
void * sNonZombie = kmalloc(15 * sizeof(char), GFP_KERNEL);
memset(sNonZombie,'\0', 15 * sizeof(char));
memcpy(sNonZombie, "NON-ZOMBIE", 12);
struct task_struct *p ;
printk("<1>Starting Iteration\n");
for (;;)
{
for_each_process(p) {
// Iterating each element of task list.
printk ("Task List::Process PID = %i,::PPID = %i,::State = %ld ::Zombie=%s\n",
p->pid,p->parent->pid,p->state, (p->state == TASK_ZOMBIE)? (char*)sZombie :
(char*)sNonZombie);
}
schedule_timeout(30*HZ); // sleep for 30 seconds.
}
// Release memory allocated via kmalloc.
kfree(sZombie);
kfree(sNonZombie);
printk("<1> Exiting Thread Function::");
}
To create a new thread, use kernel_thread() function and pass an address of thread
function as an argument to this.
kernel_thread((int (*)(void *))print_taskinfo,NULL, 0);
To test this module, just create a simple program to create a zombie process.
int main()
{
pid_t pid;
if ((pid = fork()) < 0)
exit(1);
else if (pid == 0)
exit(0);
sleep(120);
exit(0);
}
Execute above code and just see an output of ps command which depicts that child
process had died before a parent process. And as parent process has not waited on a
child process, so we have a zombie process also called < defunct > .
Output of ps command:
dahuja 2099 1998 6 11:33 pts/3 00:00:01 ./Example10_3
dahuja 2100 2099 0 11:33 pts/3 00:00:00 [Example10_3 < defunct >]
dahuja 2108 1835 0 11:33 pts/0 00:00:00 grep Example10
Output of dmesg command:
Task List::Process PID = 2078,::PPID = 2076,::State = 1 ::Zombie=NON-ZOMBIE
Task List::Process PID = 2082,::PPID = 2076,::State = 1 ::Zombie=NON-ZOMBIE
Task List::Process PID = 2083,::PPID = 2076,::State = 1 ::Zombie=NON-ZOMBIE
Task List::Process PID = 2099,::PPID = 1998,::State = 1 ::Zombie=NON-ZOMBIE
Task List::Process PID = 2100,::PPID = 2099,::State = 8 ::Zombie=ZOMBIE
Task List::Process PID = 2109,::PPID = 1924,::State = 0 ::Zombie=NON-ZOMBIE
Task List::Process PID = 2110,::PPID = 2109,::State = 0 ::Zombie=NON-ZOMBIE
We could see that our module has shown us that a zombie process exists i.e. PID=2100.
|
Thnx
Linux Kernel Thread
I already wrote a documentation in steps myself. If you need it, email me.
A good engineer, needs to document it properly. Please do NOT copy and paste from some others work. It is against the ethics, read IEEE ethics online.
Open source, sharing information is cool. However, again, you MUST document in steps, make it clear, define platforms, kernel versions, any other info etc.
Thanks
CM
I already wrote a documentation in steps myself. If you need it, email me.
A good engineer, needs to document it properly. Please do NOT copy and paste from some others work. It is against the ethics, read IEEE ethics online.
Open source, sharing information is cool. However, again, you MUST document in steps, make it clear, define platforms, kernel versions, any other info etc.
Thanks
CM
Please send the document,
my existing mode is GUI.