Assembly language, as any other structural language, allows programmers to write programs with one path of execution. There may be branches and loops, but there's always only one thing done at a time.
Thread allow running many independent paths, which will be executed in parallel. This gives big possibilities to programs doing more than one thing at a time (like reading from one file and writing processed data to the disk). Also network programs can have a gain, especially the servers. Using threads allows more than one client connecting at a time. Let's move on to the details then.
First I'm going to describe 3 functions from the C library (strictly speaking, from the pthreads library), which will allow us to manage threads.
This function accepts 4 parameters. Starting from the left (last put on the stack), these are:
This function finishes the current thread. The value given as its only parameter (address of data) can be used by threads joined (pthread_join) to this thread. After closing all the threads, the program finishes with error code 0.
Of course, the operating system assigns processor time to thread, but by calling this function we can tell it to shorten the time for this thread, thus give more to others. This is useful when the current thread has temporarily finished work (like when there's no data to process). This function has no parameters.
Below is a short program, which will show you how this works. This program's job is to display the first string once in the main function and display the second string 5 times in the thread function.
; Example multithreaded program in assembly ; ; Author: Bogdan D., bogdandr (at) op.pl ; ; assembly: ; nasm -O999 -f elf -o watki.o watki.asm ; gcc -o watki watki.o -lpthread section .text global main ; external function declarations extern pthread_create extern pthread_exit main: mov eax, 4 mov ebx, 1 mov ecx, napis1 mov edx, napis1_dl int 80h ; print string one push dword 0 ; extra data push dword watek ; address of function to run push dword 0 ; attributes push dword id_watku ; where to put the ID call pthread_create ; create new thread ; Do not exit the program with sys_exit (EAX=1), because ; this would stop all the threads of the program. We close only the main ; thread instead. push dword 0 call pthread_exit ; stop the current thread watek: mov dword [t1+timespec.tv_nsec], 0 mov dword [t1+timespec.tv_sec], 5 ; 5 seconds mov esi, 5 ; the second string will be printed 5 times .petla: mov eax, 162 ; sys_nanosleep mov ebx, t1 ; address of a structure telling ; how much time to wait mov ecx, 0 int 80h ; make a pause... mov eax, 4 mov ebx, 1 mov ecx, napis2 mov edx, napis2_dl int 80h ; print string two dec esi jnz .petla ; loop if ESI != 0 push dword 0 call pthread_exit ; exit current thread section .data napis1 db "Main function.", 10 napis1_dl equ $ - napis1 napis2 db "thread.", 10 napis2_dl equ $ - napis2 struc timespec .tv_sec: resd 1 .tv_nsec: resd 1 endstruc t1 istruc timespec id_watku dd 0 ; variable which will get the new thread's ID
But having threads in a program isn't just the good stuff. The biggest problem in multithreaded programs is thread synchronizing.
Why synchronize? To make sure that the program will not run into trouble when two or more threads read and write the same global variable (like a data buffer).
What should you do to make the reader thread process the data only when another thread writes the data? There are a few possibilities:
Say for example, that if the flag is zero, the buffer can be freely used (for reading and writing). If the flag is 1, no buffer operation can be done (because some other thread is operating on it) - the thread has to wait until the flag becomes zero.
The advantage of this solution is its simplicity. Look:
flag db 0 ... thread: ... check_flag: cmp byte [flag], 1 je check_flag mov byte [flag], 1 ... ; our operations go here mov byte [flag], 0
As you can see, writing multithreaded programs is not that hard, and it's worth learning. Usually the gain is bigger (writing one function for each task) than the work (synchronization).
You don't have to use any libraries to write multithreaded programs. The needed mechanisms are also provided by the system interface - int 80h.
I'm going to use the system function sys_fork (number 2) here. Its only parameter is an address of a structure containing the register values for the new process, but this parameter is optional and can be a zero. The fork function returns a negative value in case of error, zero in the child process and a positive value (the child's process ID) in the parent process. The child process starts execution right after the call to fork, thus the parent and the child start executing exactly the same code. You can direct the processes to new paths using the return value in EAX.
Here's a short example in FASM syntax:
format ELF executable entry _start segment executable _start: mov eax, 2 ; the fork function number xor ebx, ebx int 80h ; call it cmp eax, 0 jl .koniec ; EAX < 0 means error ; the below instructions will be executed by both the parent and the child: cmp eax, 0 jg .rodzic ; EAX > 0 means this is the parent process ; here neither EAX < 0, nor EAX > 0, so EAX=0, so ; this is the child process ; the below code (printing and waiting) will be executed only ; by the child process mov dword [t1.tv_nsec], 0 mov dword [t1.tv_sec], 5 ; this many seconds will the pause ; between printing last .petla: mov eax, 4 ; file writing function mov ebx, 1 ; standard output mov ecx, napis2 ; what to write mov edx, napis2_dl ; length of the string int 80h mov eax, 162 ; sys_nanosleep mov ebx, t1 ; wait this much mov ecx, 0 ; an address of a second timespec structure ; can be put here int 80h ; do the pause... jmp .petla ; and start all over again.... ; the below code (printing and exit) will be executed only ; by the child process .rodzic: mov eax, 4 ; file writing function mov ebx, 1 ; standard output mov ecx, napis1 ; what to write mov edx, napis1_dl ; length of the string int 80h .koniec: mov eax, 1 ; exit the program xor ebx, ebx int 80h segment readable writeable napis1 db "Parent", 10 napis1_dl = $ - napis1 napis2 db "Child", 10 napis2_dl = $ - napis1 struc timespec ; timespec structure definition ; (only as a data type) { .tv_sec: rd 1 .tv_nsec: rd 1 } t1 timespec ; t1 is a variable representing the whole structure