In Linux, the thread library-general Linux technology-Linux programming and kernel information is implemented. The following is a detailed description. Original article: Implementing a Thread Library on Linux
By Evan Jones
Translation: Yang Shuo
When a program is running, there is usually only one main line. In some cases, this is inefficient. There are many programs that must be able to do a lot of work at the same time. For example, a GUI program must redraw the interface while processing the program logic to respond to user input. The Web server must send messages to hundreds of customers at the same time. The thread can solve these problems. All modern operating systems provide thread libraries, such as standard Posix Threads. In particular, I am very curious about the working principle of the thread, so after learning some knowledge about C and Linux kernel, I began to build my own thread library.
Multi-task
In traditional operating systems, two tools are provided to implement multi-task, one is process and the other is thread.
Process
Each program is an independent process. The operating system allocates resources among all processes. To prevent a process from modifying the resources of other processes, processes are completely independent of each other. However, it is also common that multiple processes work together to complete a task. For example, it shows the processes that are simultaneously executed in my Linux system. Four of them are executing mozilla-bin. In this case, processes need to communicate with each other. Therefore, the operating system provides tools for inter-process communication (IPC), such as signals, shared memory, and pipelines.
Process ID Command
1 init
142/sbin/syslogd
183/usr/bin/X11/X
266 esd
270 sawfish
275 panel
277 gmc
279 gnome-terminal
291 bash
319/usr/bin/mozilla-bin
341/usr/bin/scite
344/usr/bin/mozilla-bin
345/usr/bin/mozilla-bin
346/usr/bin/mozilla-bin
2568 ps
Process List on Linux
Thread
Inter-process communication is very simple and easy to use. However, if many processes need to share their resources, the model will become very complex. The thread provides a simple and efficient way to solve such problems. A process can contain multiple threads. All resources except the environment context (stack and CPU register) are shared among threads. Therefore, if a thread modifies a shared resource, this change is visible to other threads. The disadvantage of a thread is to introduce a new problem, that is, to avoid two threads simultaneously accessing a shared resource.
Kernel thread
The modern operating system supports threads at the kernel level. This means that the thread is directly scheduled by the operating system scheduler, which can greatly improve the performance. The disadvantage of kernel threads is that thread switching costs a lot. Therefore, concurrent threads may be less efficient than single threads because thread switching takes some time. This is also the reason why single-threaded servers, such as thttpd, have better performance than multi-threaded servers, such as Apache. JAWS research project deeply analyzes the performance of web servers implemented using different multitasking methods.
User thread
Threads can also be implemented at the user level, which means that thread scheduling is completed by the program itself or the thread library. If the thread is implemented at the user level, there will also be a large thread switching overhead, but this overhead is much smaller than the thread switching at the kernel level. Sometimes, a user thread is called a fiber, suggesting that it is "lighter" than a kernel thread ". A simple thread Library
Libfiber was born to fully play with these concepts. It Implements user thread and kernel thread. This thread Library only implements thread creation, destruction, and scheduling. It can only be used for teaching, because there are still many problems of signal and synchronization that have not been solved. When writing a real program, you can use the kernel thread library pthreads or the user thread library GNU Portable Threads (Pth ).
Libfiber Interface
Void initFibers ();
Initializes the internal data structure of the thread library. It must be called before other functions are used.
Int spawnFiber (void (* func) (void ));
Creates a thread that executes the given function. Zero is returned for success, and non-zero is returned for errors.
Void maid ();
Voluntarily discard the CPU. In kernel thread implementation, sched_yield is simply called.
Void waitForAllFibers ();
Wait for all threads to exit and release resources. If it is called in a user thread, only all other threads return this function. If it is called in a kernel thread, it will return immediately. Zero is returned for success, and non-zero is returned for errors.
Shows the sample program written in this thread library. It creates three threads and executes different functions respectively, waiting for all threads to exit and return.
Int main ()
{
// Initialize the fiber library
InitFibers ();
// Since these are not preemptive, we must allow them to run
WaitForAllFibers ();
// The program quits
Return 0;
}
Sample programs using the thread Library
Implement kernel threads on Linux
Linux uses a special method to implement threads. In Linux, processes and threads are essentially the same, and they are considered as tasks. The only difference is the off-the-shelf shared memory space, file descriptors and signal processing programs (usually ). On the Linux Kernel Mailing List, Linus Torvalds posted a classic post to describe the advantages of this implementation method.
On Linux, kernel threads can be created by calling clone. Similar to fork, this system call creates a task. Only clone can specify the resources to be shared. When creating a thread, we can share as many resources as possible: memory space, file descriptors, and signal processing functions. When the thread exits and the SIGCHLD signal is sent, wait will return.
The first challenge is to allocate stack space for threads. In libfier, the simplest method is to use malloc to allocate stack space in the heap. This means that the maximum value of a stack must be estimated. If the maximum value of the stack is exceeded during use, memory conflicts may occur. In bb_threads, mprotect is used to create a barrier at the bottom of the stack, which will cause a segment error when the stack overflows. The best way is to implement Linux pthreads by using mmap to allocate memory. In this way, the memory will be allocated as needed. If the system cannot allocate another memory, segmentation violation will be generated.
The stack pointer passed to clone must point to the top of the stack, because in most processors, the stack grows down. To avoid Memory leakage, the stack space must be released when the thread exits. The Libfiber library uses wait to wait for the thread to exit and then uses free to release the stack space. The example of creating a thread is displayed. View the complete implementation through the libfiber-clone.c. If you want to know how to create a thread for Linux pthread, see "The ERS ers of Threads ".
# Include
# Include
# Include
# Include
# Include
# Include
// 64 kB stack
# Define FIBER_STACK 1024*64
// The child thread will execute this function
Int threadFunction (void * argument)
{
Printf ("child thread exiting \ n ");
Return 0;
}
Int main ()
{
Void * stack;
Pid_t pid;
// Allocate the stack
Stack = malloc (FIBER_STACK );
If (stack = 0)
{
Perror ("malloc: cocould not allocate stack ");
Exit (1 );
}
Printf ("Creating child thread \ n ");
// Call the clone system call to create the child thread
Pid = clone (& threadFunction, (char *) stack + FIBER_STACK,
SIGCHLD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_VM, 0 );
If (pid =-1)
{
Perror ("clone ");
Exit (2 );
}
// Wait for the child thread to exit
Pid = waitpid (pid, 0, 0 );
If (pid =-1)
{
Perror ("waitpid ");
Exit (3 );
}
// Free the stack
Free (stack );
Printf ("Child thread returned and stack freed. \ n ");
Return 0;
}
Use clone to implement thread
Use makecontext to Implement User threads
All modern Unix systems provide context switching functions in ucontext. h. These functions include getcontext, setcontext, swapcontext, and makecontext. Getcontext is used to save the current context. setcontext is used to switch the context. swapcontext stores the current context and switches to another context. makecontext creates a new context. The process of implementing the user thread is: first call getcontext to obtain the current context, and then modify ucontext_t to specify the new context. Similarly, we need to open up stack space, but the thread library implemented this time involves the direction of stack growth. Then we call makecontext to switch the context and specify the function to be executed in the user thread.
There is another challenge in this implementation, that is, a thread must be able to actively give out the CPU to other threads. The swapcontext function can complete this task and demonstrate a sample program for this implementation. The child thread and the parent thread continuously switch to achieve the effect of multithreading. You can see the complete implementation in the libfiber-uc.c file.
# Include
# Include
# Include
// 64 kB stack
# Define FIBER_STACK 1024*64
Ucontext_t child, parent;
// The child thread will execute this function
Void threadFunction ()
{
Printf ("Child fiber yielding to parent ");
Swapcontext (& child, & parent );
Printf ("Child thread exiting \ n ");
Swapcontext (& child, & parent );
}
Int main ()
{
// Get the current execution context
Getcontext (& child );
// Modify the context to a new stack
Child. uc_link = 0;
Child. uc_stack.ss_sp = malloc (FIBER_STACK );
Child. uc_stack.ss_size = maid;
Child. uc_stack.ss_flags = 0;
If (child. uc_stack.ss_sp = 0)
{
Perror ("malloc: cocould not allocate stack ");
Exit (1 );
}
// Create the new context
Printf ("Creating child fiber \ n ");
Makecontext (& child, & threadFunction, 0 );
// Execute the child context
Printf ("Switching to child fiber \ n ");
Swapcontext (& parent, & child );
Printf ("Switching to child fiber again \ n ");
Swapcontext (& parent, & child );
// Free the stack
Free (child. uc_stack.ss_sp );
Printf ("Child fiber returned and stack freed \ n ");
Return 0;
}
Use makecontext to implement threads
Conclusion
Implementing a simple thread library libfiber allows us to better understand how the thread library implements threads. When we are analyzing different multithreading tools, these experiences will be useful. This thread library is developed and tested in Linux, but the real thread library can be transplanted to the Unix platform. In this regard, the kernel thread implemented in this thread library is exclusive to Linux because it is implemented by calling the clone system.
The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion;
products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the
content of the page makes you feel confusing, please write us an email, we will handle the problem
within 5 days after receiving your email.
If you find any instances of plagiarism from the community, please send an email to:
info-contact@alibabacloud.com
and provide relevant evidence. A staff member will contact you within 5 working days.