Site Loader


Yash Tare1Computer Science, Volgenau School of Engineering,George Mason University, Fairfax, Virginia, United States of [email protected] Thread programming being a vital aspect of building the Operating System, it’s behavior is the most crucial part that an OS programmer must be mindful of. The POSIX library provide a very useful interface in modifying the behavior of the thread through the C structure of thread attributes. Since the concept of thread is based on the fact that all threads in a process share the same virtual memory, and other resources, as against the process that they belong to, which has a separate well dedicated virtual memory, it becomes extremely important that these threads see all those resources in a consistent manner.

Also concerns arise regarding scheduling and the degree of concurrency of those threads. We now explore the ways and means to manage the behavior of the treads in a process with respect to each other.Keywords— threads, thread attributes, POSIX, pthread Library. LWP (light weight processes), multithreading, routines, stacks, addresses, virtual memory, concurrency, schedulingTable of ContentsSr No.TitlePage no.IIntroduction1IIAttachable and Detachable Threads2IIIStacks in Threads3IVThread Scope and Concurrency3VScheduling Policy4VIDefault Values5References6Sr no.Figure NamesPage No.

1Multithreading12Thread Structure and Processes23Thread Initialization24Destroying the thread attribute25Joinable Threads Program36Detached Threads37getstacksize() prototype38setstacksize() prototype39Guardsize routines310Bound and Unbound Threads411Setting a process scope412Getting a Process Scope413Setting Concurrency level414Setting Thread Concurrency415Thread Scheduling516Getting the Scheduling Policy517Inherited Scheduling518Getinheritsched() prototype519Setting the scheduling parameter520Default values of Attribute Objects6I. INTRODUCTIONThread attributes is a structure in C which can be used as an argument during the creation of thread to specify its implementation. An attribute object is created and initialized and the same can be modified later before using it in the thread by calling various routines in the pthread library. We will first understand the concept of thread so that we better understand the context in which thread attributes are implemented.

A thread is the basic unit of CPU utilization meaning that the smallest possible sequence of instruction is passed to the CPU as instruction-set for computation. Each thread has its own thread_ID, Program Counter, Register Set, and Stack. A process when executes runs as a single threaded heavy weight process. When a new thread of instructions within a process are separated for concurrent execution, the process runs with different instructions running concurrently within the same program. All these instruction-sets or threads share the same memory space and many other resources like the global variables in the program etc. It is worth noting that it might as very well be that 2 threads would require accessing and even modifying the same address space.

To make things terser, every process is also an address space. Hence creating a new process involves creating an address space. But creating a thread implies working within the same address space. So, if a thread modifies an address location, without the consent of the other threads, it will result into data inconsistency. This is the primary reason due to which threads are so less expensive. The thread attribute come into picture here. The attributes that we assign to a thread while creating it, will determine the implementation.

Fig 1: MultithreadingFig 2: Thread Structure within ProcessesAn attribute structure holds a series of objects carrying different parameters which will determine the nature of the threads execution. As mentioned earlier, after the attribute creation and initialization, it can be modified but only before the execution of the threads. We can create any number of such attributes and later use them as and when required. This naturally allows us the advantage of code portability.This also makes the attribute object opaque, in the sense that the calling functions and threads cannot access the individual objects of the attribute structure. Thus, we can summarize the advantages of thread attributes as following: -• Code portability• Simplified state specificationWe now explore different aspects of thread behaviors based on the routines that enable them to behave so.

Thread initialization is done by the routine, pthread_attr_init(). Following is its prototype: -int pthread_attr_init(pthread_attr_t *tattr);#include pthread_attr_t tattr;int ret;/* initialize an attribute to the default value */ret = pthread_attr_init();Fig 3: Thread initialization.This routine initializes the object variables of the attribute structure to the default values and the storage allocated accordingly by the thread system during runtime. A successful initialization returns 0 while any other integer returned corresponds to an error.

Error ENOMEM implies that there is no enough memory to initialize more attributes. Also during the process exit time. it is important that the memory must be returned to the system. Every attribute must be destroyed after it is no longer needed. The pthread_attr_destroy() routine removes all the storage allocated during initialization, following being it’s prototype :int pthread_attr_destroy(pthread_attr_t *tattr);#include pthread_attr_t tattr;int ret;/* destroy an attribute */ret = pthread_attr_destroy(&tattr);Fig 4: Destroying the thread attributeII. ATTACHABLE AND DETACHABLE THREADSThreads can be broadly categorized into attachable(joinable) and detachable threads.

By default, all threads are Joinable. If threads are joinable, then it is assumed that further in the calling thread, pthread_join() routine will be called. This is because, joinable thread waits for the called thread to complete all its execution and then only can it proceed to its own completion. While the 2 threads are executed concurrently, the calling thread comes across the join routine. At this point, the calling thread goes into suspended state. As the calling thread halts its execution, the called thread first completes its execution, and all the resources are assigned back to the calling thread and its resumes back its execution. In this method the disadvantage is that the resources of the new thread cannot be released to be reclaimed by the OS. Following is an example of the implementation of joinable threads:#include

h>#include #define NUM_THREADS 5void *print_hello(void *threadid) {long tid;tid = (long) threadid;printf(“Hello World! It’s me, thread #%ld!”, tid);pthread_exit(NULL);}int main (int argc, char *argv) {pthread_t threadsNUM_THREADS;int rc;long t;for (t = 0; t < NUM_THREADS; t++) {printf("In main: creating thread %ld", t);rc = pthread_create(threads + t, NULL, print_hello, (void *) t);if (rc) {printf("ERROR; return code from pthread_create() is %d", rc);exit(-1);}}/* wait for all threads to complete */for (t = 0; t < NUM_THREADS; t++) {pthread_join(threadst, NULL);}pthread_exit(NULL);}Fig 5: Joinable Threads ProgramOn the other hand, if the thread is implemented in as a detachable one, the calling method completes its execution without waiting for the called thread to finish its execution.Here the resources can be released as soon as the called thread completes its execution. A thread which does not depend on its calling thread is advisable to be created as a detached thread. This is because, if implementing threads in a joinable way holds the resources of the OS until the execution of the process. This the amount of unnecessarily held resources increases and it leads to memory leak error. Also the thread ID can be assigned to another thread as soon as it dies in cases of detachable threads. The pthread_attr_setdetachstate() and The pthread_attr_setdetachstate() routines are used to check and change the detachstate of an attribute.

Following is a prototype of Detachable threads:int pthread_attr_setdetachstate(pthread_attr_t *tattr,int detachstate);#include pthread_attr_t tattr;int ret;/* set the thread detach state */ret = pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_DETACHED);Fig 6: Detached ThreadsIII. STACKS IN THREADSWe know that every thread holds its own stack. When a new stack is assigned to a newly created thread, a space in the virtual memory that starts from a page boundary is set as the beginning of the stack. By default, the system assigns 1 MB of virtual memory to each stack with no swap. The stack pointer is the address of the starting or bottom most element of the stack. The stack size is used to obtain the topmost element of the stack. Thus, the stack-size comes into picture which keeps reducing and growing. The pthread_attr_getstacksize() and pthread_attr_setstacksize() routines are used accordingly.

Following are the prototypes of the same respectively:int pthread_attr_getstacksize(pthread_attr_t *tattr, size_t *size);#include pthread_attr_t tattr;int size;int ret;/* getting the stack size */ret = pthread_attr_getstacksize(&tattr, &size);Fig: 7: getstacksize() prototypeint pthread_attr_setstacksize(pthread_attr_t *tattr, int size);#include pthread_attr_t tattr;int size;int ret;size = (PTHREAD_STACK_MIN + 0x4000);/* setting a new size */ret = pthread_attr_setstacksize(&tattr, size);Fig: 8: setstacksize() prototypeEvery stack is also assigned a Red Zone which is a page in the virtual memory that is appended on top of the stack with no access permissions. If this section is accessed, then the SIGSEV signal is triggered implying the buffer overflow error. The guardsize argument provides protection against overflow of stack pointer. This prevention from overflow provides protection from wastage of resources. Also, when large data structures are allocated on the stack, a larger guard area may be needed to detect overflow. The routines pthread_attr_getguardsize() and pthread_attr_getguardsize() can be used to know and alter the guardsize.

Following are the prototypes:#include int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);#include int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize);Fig 9: Guardsize routinesThe stack size and guard size are rounded up to the next page boundaries. If a stack is explicitly created in a thread rather that thesystem assigned stack, then the guard size of that stack is set to 0 by default and it is assumed that the programmer will take care of the overflow error. The OS will not bother to alert the programmer of any stack-overflow errors. There might be several situations when we might have to consider increasing the stack size. If there are many threads in a process and the cumulative size of the stack is large, then it makes sense to reduce the size of individual stack. On the other hand, if the thread calls functions that allocated large automatic variables or function calls that are many stack frames deep, then we could consider increasing the stack size. If we eventually run out of virtual memory, we could use malloc or mmap to allocate space for alternate stack and use the pthread_attr_setstack() routine to access the desired stack.

To change the stack location of the threads we can use the stackaddr parameter which, as mentioned earlier, is the pointer to the bottom most element of the stack. Practically though seldom should you consider creating a different stack explicitly and using it in a thread as even an expert cannot confirm the size that will be required by the stack.IV. THREAD SCOPE AND CONCURRENCYAs we know concurrency is the prime reason behind the foundation of the concept of threads, every thread has either of the 2 scopes viz. Process scope and System Scope. To understand scopes and concurrency though we need to delve deeper into the multi-threading environment. In the multithreading environment, threads are classified into User Level threads and Kernal level threads.

Both are executed in their respective spaces viz. the user space and kernel space respectively. Most application level threads are user level threads.

How many kernel resources are required by the will be utilized by the application depends on its functionality and usage. The user level threads will be visible only from within the process. Thus, threads within a process can communicate without the intervention of the OS. But when this lightweight thread is bound to the execution resources by the application, it becomes a kernel level thread.Fig 10: Bound and unbound threadsNow the user level threads are mapped to the kernel level threads using different models viz.

many to one, one to one and many to many.The thread with the process scope are also called as unbound threads and are executed in the user space. These threads can very well be attached and detached to any other available light weight process in the pool. Having the threads in the process scope, increases the performances as the threads float within the pool of LWPs. The PTHREAD_SCOPE_PROCESS is the parameter which implies that the thread has a process scope.

On the other hand, the threads with the system scope are called bound threads. These threads are attached to an LWP for their entire lifetime. We can change the scope of a thread attribute by the pthread_attr_setscope() routine, its prototype being as following:int pthread_attr_setscope(pthread_attr_t *tattr,int scope);#include pthread_attr_t tattr;int ret;/* bound thread */ret = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM);/* unbound thread */ret = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_PROCESS);Fig 11: Setting a process scope.Also we could obtain the scope of a process form the pthread_attr_setscope() routine:int pthread_attr_getscope(pthread_attr_t *tattr, int *scope);#include pthread_attr_t tattr;int scope;int ret;/* get scope of thread */ret = pthread_attr_getscope(&tattr, &scope);Fig 12: Getting a process scope.

As regarding concurrency, the number of threads that are active simultaneously, is in question. If the thread model is one to one, concurrency is irrelevant. But if many user level threads are mapped to one or many kernel level threads, the number of user level threads running simultaneously can be changed to utilize the system resources optimally. Here we could user the pthread_setconcurrency() routine. The new_level parameter determines the number co concurrent threads that can run simultaneously.

If the value of new_level is 0, then the system determines the concurrency level. Following is its prototype:#include int pthread_setconcurrency(int new_level);Fig 13: Setting Concurrency levelHere if the error received is EAGAIN, then it means that the value specified by new_level would cause a system resource to beexceeded. Also, the new_level value can be obtained by the pthread_getconcurrency() function. Prototype:#include int pthread_getconcurrency(void);Fig 14: Setting thread concurrencyV.

SCHEDULING POLICYFollowing standard scedulinh policies are specified by POSIX:• FIFO (First In First Out) – SCHED_FIFOIf these threads are not pre-empted by a thread of higher priority, it completes its execution. The first to be created will be the first to be executed.• Round-Robin Algorithm – SCHED_RRIf these threads are not pre-empted by a thread of higher priority, then they will execute for a certain time slice.• Other – SCHED_OTHERThey run as implemented by the application’s program,The scheduling policy can be set to any of the above 3 mentioned policies. By default, the policy is SCHED_OTHER which is an implementation defined policy.

The scheduling policy can be set by the pthread_attr_setschedpolicy() function. Prototype:int pthread_attr_setschedpolicy(pthread_attr_t *tattr, int policy);#include pthread_attr_t tattr;int policy;int ret;/* set the scheduling policy to SCHED_OTHER */ret = pthread_attr_setschedpolicy(&tattr, SCHED_OTHER);Fig 15: Thread SchedulingSimilarly, the policy in an attribute can be obtained by pthread_attr_getschedpolicy() function. Prototype:int pthread_attr_getschedpolicy(pthread_attr_t *tattr, int *policy);#include pthread_attr_t tattr;int policy;int ret;/* get scheduling policy of thread */ret = pthread_attr_getschedpolicy (&tattr, &policy);Fig 16: Getting the Scheduling PolicyError ENOTSUP here implies that the attempt was made to set the scheduling policy to an unsupported value.Also, an inherit value of PTHREAD_INHERIT_SCHEDULE indicates that the scheduling policies in the creating thread are to be inherited by this new thread.

In this case, the policies defined in the creating thread are ignored. If PTHREAD_EXPLICIT_SCHED is specified then, the policies in the pthread_create() are used. Prototype:int pthread_attr_setinheritsched(pthread_attr_t *tattr, int inherit);#include pthread_attr_t tattr;int inherit;int ret;/* use the current scheduling policy */ret = pthread_attr_setinheritsched(&tattr, PTHREAD_EXPLICIT_SCHED);Fig 17: Inherited schedulingSimilarly, we can get the schedule policy as set by the pthread_attr_setinheritsched() by the pthread_attr_getinheritsched() routine. Prototype:int pthread_attr_getinheritsched(pthread_attr_t *tattr, int *inherit);#include pthread_attr_t tattr;int inherit;int ret;/* get scheduling policy and priority of the creating thread */ret = pthread_attr_getinheritsched (&tattr, &inherit);Fig 18: getinheritsched prototypeThe pthread_attr_setschedparam() function sets the scheduling parameters in the thread attributes object. Attempting to set the sched_policy field of the param parameter other than SCHED_OTHER causes the EINVAL error. Thesched_priority field of the param parameter must range from PRIORITY_MIN to PRIORITY_MAX or the ENOTSUP error occurs. The prototype goes as following:int pthread_attr_setschedparam(pthread_attr_t *tattr, const struct sched_param *param);#include pthread_attr_t tattr;int newprio;sched_param param;newprio = 30;/* set the priority; others are unchanged */param.

sched_priority = newprio;/* set the new scheduling param */ret = pthread_attr_setschedparam (&tattr, &param);Fig 19: Setting the Scheduling parameterVI. DEFAULT VALUESWe now compile the default values of all the objects in the attribute structure.AttributeValueResultscopePTHREAD_SCOPE_PROCESSNew thread is unbound – not permanently attached to LWP.detachstatePTHREAD_CREATE_JOINABLEExit status and thread are preserved after the thread terminates.stackaddrNULLNew thread has system-allocated stack address.stacksize1 megabyteNew thread has system-defined stack size.priorityNew thread inherits parent thread priority.inheritschedPTHREAD_INHERIT_SCHEDNew thread inherits parent thread scheduling priority.

schedpolicySCHED_OTHERNew thread uses Solaris-defined fixed priority scheduling; threads run until preempted by a higher-AttributeValueResultpriority thread or until they block or yield.Fig 20: Default values of attribute objectsREFERENCES1 W. Richards Stevens, Stephan A. Rago, Advanced Programming in the UNIX.2 Oracle Docs : Abraham Silberschatz, Peter Baer Galvin and Greg Gagne, Operating System Concepts.

Post Author: admin


I'm Dora!

Would you like to get a custom essay? How about receiving a customized one?

Check it out