0% found this document useful (0 votes)
76 views30 pages

Operating Systems Chapter 4

This document discusses operating system processes and threads. It describes how threads allow multiple execution paths within a single process, sharing the process's resources. Threads avoid the overhead of process switching and enable fast inter-thread communication. The document uses Lynx, a real-time Unix-based OS, as an example and describes how it implements threads to allow decomposing real-time control applications into parallel time-bound tasks. Server applications are also discussed as another common use of threads. The UNIX process model is summarized, distinguishing user and kernel execution states, preempted processes, and zombie processes.

Uploaded by

sharanabasappad
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
76 views30 pages

Operating Systems Chapter 4

This document discusses operating system processes and threads. It describes how threads allow multiple execution paths within a single process, sharing the process's resources. Threads avoid the overhead of process switching and enable fast inter-thread communication. The document uses Lynx, a real-time Unix-based OS, as an example and describes how it implements threads to allow decomposing real-time control applications into parallel time-bound tasks. Server applications are also discussed as another common use of threads. The UNIX process model is summarized, distinguishing user and kernel execution states, preempted processes, and zombie processes.

Uploaded by

sharanabasappad
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 30

Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

Operating Systems

Contents

- 1 . Introduction
- 2. Structure of operating systems
- 3. Processes in Operating Systems
- 4. Threads
- 5. File Management
- 6. Concurrency
- 7. Memory management
- 8. Scheduling
- 9. File Management systems
- 10. Networking
- 11. Introduction to InterProcess Communication
- 12. Appendix : Common Unix utilities

4.Threads

In previous sections the concept of context switch has been differentiated from that of process
switch, and it has been remarked that performing an OS service, while usually requiring a
context switch, not always makes a process switch necessary, depending on the scheduling
policy adopted by the OS.

1 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

A different way to regard the same issue consists in differentiating between two independent
attributes involved by the concept of process, namely:

- Unit of resource utilization (for resources other than CPU time);


- Unit of dispatching;

The former refers to the fact that the management of a process involves, on the OS side, the
``investing'' of resources in terms of allocated main memory, OS table update, availability of I/O
streams and devices.

The latter points at the process as being the execution of a program, which may be interleaved
with that of other processes in a multiprogramming environment, and involves the management
of process states, priority, queues, etc.

Now it should be noted that, even if in traditional operating systems the above attributes
uniquely coexist within one process, nothing in the above forbids considering a collection of
programs , all
sharing resources, as a unit of dispatching. The recognition of the independence of these two
aspects has lead to the invention of
threads
or
lightweight processes
.

Though the relatively recent concept of thread has found different implementation in various
operating system, the most common one in an arrangement in which multiple threads coexist
within one process. In this case threads can be easily visualized as as parallel traces of the
same process independently executed (think of something like having multiple program
counters on the same instruction list - that of the process). This approach has been
implemented in OS/2, Sun OS, Lynx and, most notably, Mach.

In Lynx, a real-time, Posix abiding OS, processes are implemented in much the same way as is
usual in UNIX family: each process is allowed an address space in (virtual) memory which is
protected from other processes' interference, the kernel executes mostly in the user process
context and keeps in the user area of a process all the process-related information that is

2 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

needed only when the process is running - most notably the kernel stack. However there are
two major differences with other ``traditional'' unices (e.g BSD, System V, Ultrix, USD, etc.):
Lynx is a real-time operating system and allows for the spawning of ``parallel threads'', or
pthreads, within a process (because this is about the only way to be real-time for real).

Real-time means essentially that the OS guarantees that it'll honor an interrupt request within a
fixed response time, which is comparable to a context-switch delay. This is essential for most
application involving automatic control, where a computer (really a controller program sitting on
top of the OS) is put in a feedback loop between some machinery controlling a plant and
devices sensing the plant's behaviour. An unpredictable response time for the controller makes
the control design problem very difficult, if not altogether impossible. Yet traditional unices
cannot
be interrupted while executing in kernel mode (disabling the CPU's interrupt pins is about the
first thing that both system calls and system utilities do when they gain control), and hence the
interrupt response time can be as long as the execution time of the longest system routine. Lynx
can be interrupted in kernel mode, instead, and also offers some quite sophisticated ``timer''
system calls for finely tuning the timing of the application's operations.

Since fast controller's are always welcome in control application (you can always slow them
down if you want, while boosting a FIAT into a Ferrari is still the subject of basic research...),
Lynx also offer pthreads, which allow a designer to nicely decompose the control application in
parallel time-bound tasks (maybe operating at radically different time scales), without incurring
in a process switch overhead, and with the bonus of fast inter-thread communication through a
shared memory area. Each pthread has the following attributes:

- Thread's execution state (Running, Ready, etc.);


- Saved thread processor context when not running (so there's one context per thread, plus
the process context).
- Thread's execution stack and local variables;
- Pointers to resources (memory, I/O buffers, timers, etc.) shared with all the other pthreads
within the same process.

Note that operations like suspension (i.e. swapping to disk) are still performed at the process
level, and they affect all the pthreads within a process when they occur.

A very common application of threads outside real-time control is on the server side of
client-server application over computer networks. In a typical scenario for these applications, a

3 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

server program sits idle listening to a communication port, waiting for service requests from
remote clients. When one arrives, according to how it's designed, it can:

- Service the request by itself, in the meantime denying service to other clients. Obviously
this works only for very simple & quick kinds of services, and when only a small number of
service requests may arrive within one service time interval.
- Spawn a child process which does the servicing. This initially improves the response time
on the client side, since the creation of a process is a quick event on a network's time scale. Yet
with an increasing number of active processes the burden can become excessive, with the OS
wasting too many a cycle switching between them. For this very reason, many OSs impose
quite stingy limits on the maximum allowable number of active processes, refusing to spawn
new ones when the limit is exceeded.
- Delegate the service to one of a pre-defined number of worker threads. This has the same
response time advantage as the previous solution, without the process switch burden.
Moreover, threading the services allows way more flexibility in the servicing application design.
For example, one can make use of the cheap inter-thread communication for chaining threads
that compute only parts of the service.

Process creation in UNIX

The seven-state logical process model we considered in a previous lecture can accommodate
the UNIX process model with some modifications, actually becoming a ten state model.

First, as we previously observed, UNIX executes most kernel services within a process's
context, by implementing a mechanism which separates between the two possible modes of
execution of a process. Hence our previously unique ``Running'' state must actually be split in a
``User Running'' state and a ``Kernel Running'' state. Moreover a process preemption
mechanism is usually implemented in the UNIX scheduler to enforce priority. This allows a
process returning from a system call (hence after having run in kernel mode) to be immediately
blocked and put in the ready processes queue instead of returning to user mode running,
leaving the CPU to another process. So it's worth considering a ``Preempted'' state as a special
case of ``Blocked''. Moreover, among exited processes there's a distinction between those
which have a parent process that waits for their completion (possibly to clean after them), and
those which upon termination have an active parent that might decide to wait for them sometime
in the future (and then be immediately notified of its children's termination). These last
processes are called ``Zombie'', while the others are ``Exited''. The difference is that the system
needs to maintain an entry in the process table for a zombie, since its parent might reference it
in the future, while the entry for an exited (and waited for) process can be discarded without

4 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

further fiddling. So the much talked about ``Zombie'' processes of UNIX are nothing but entries
in a system table, the system having already disposed of all the rest of their image. This process
model is depicted in fig. 5.

Figure 5: UNIX process state model

All processes in UNIX are created using the fork() system call. System calls in UNIX can be best
thought of as C functions provided with the standard C library. Even if their particular
implementation is depends on the particular UNIX flavor (and on hardware, for many of them), a
C API is always provided, and is consistent among the different unices, at least in the
fundamental traits.

UNIX implements through the fork() and exec() system calls an elegant two-step mechanism for
process creation and execution. fork() is used to
create the image of a process using the one of an existing one, and
exec
is used to execute a program by overwriting that image with the program's one. This separation
allows to perform some interesting housekeeping actions in between, as we'll see in the
following lectures.

A call to fork() of the form:

5 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

#include <sys/types.h>

pid_t childpid;

...

childpid = fork(); /* child's pid in the parent, 0 in the child */

...

creates (if it succeeds) a new process, which a child of the caller's, and is an exact copy of of
the (parent) caller itself. By exact copy we mean that it's image is a physical bitwise copy of the
parent's (in principle, they do not share the image in memory: though there can be exceptions to
this rule, we can always thing of the two images as being stored in two separate and protected
address spaces in memory, hence a manipulation of the parent's variables won't affect the
child's copies, and vice versa). The only visible differences are in the PCB, and the most
relevant (for now) of them are the following:

- The two processes obviously have two different process id.s. (pid). In a C program
process id.s are conveniently represented by variables of pid_t type, the type being
defined in the sys/types.h
header.

- In UNIX the PCB of a process contains the id of the process's parent, hence the child's
PCB will contain as parent id (ppid) the pid of the process that called fork(), while the caller
will have as ppid the pid of the process that spawned it.

- The child process has its own copy of the parent's file descriptors. These descriptors
reference the same under-lying objects, so that files are shared between the child and the
parent. This makes sense, since other processes might access those files as well, and having
them already open in the child is a time-saver.

6 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

The fork() call returns in both the parent and the child, and both resume their execution from the
statement immediately following the call. One usually wants that parent and child behave
differently, and the way to distinguish between them in the program's source code is to test the
value returned by fork(). This value is 0 in the child, and the child's pid in the parent.
Since fork() returns -1 in case the child spawning fails, a
catch-all C code fragment to separate behaviours may look like the following:

#include <sys/types.h>

#include <errno.h>

#include <stdio.h>

...

pid_t childpid;

...

childpid=fork();

switch(childpid)

7 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

   case -1:

      fprintf(stderr,"ERROR: %sn", sys_errlist[errno]);

      exit(1);

      break;

   case 0:

      /* Child's code goes here */

      break;

   default:

      /* Parent's code goes here */

      break;

The array of strings char *sys_errlist[] and the global integer variable int errno are defined in the
errno.h

8 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

header. The former contains a list of system error messages, and the latter is set to index the
appropriate message whenever an error occurs. For each system call several possible error
conditions are defined. Each of them is associated to an integer constant - defined via a
#define
directive in one system header file - whose value is exactly the one that
errno
takes when an error occurs. A sample definition (from the sys/errno.h header) is:

...

#define ENOMEM 12

...

which defines the error that might occur when a process creation fails because there's not
enough memory available.

Note that a child (i.e. a process whatsoever, since they are all children of some other process,
with the exception of processes 0, swapper and 1, init) cannot use the value returned by fork()
to know its pid, since this is always 0 in the child. A system call named
getpid()
is provided for this purpose, and another one, named
getppid()
is used to ask the system about the parent's id. Both functions take no arguments and return the
requested value in
pid_t
type, or -1 in case of failure.

In the above program fragment, a system call to exit() is made in case of failure, which causes
the program to abort (you might want to deal with the errors in a smoother way, depending on
your application, and perform some application-dependent error recovery action). We'll see later
that the exit() call returns the lower 8 bits
of its argument (1, in the above example) to a waiting parent process, which can use them to
determine the child's exit status and behave accordingly. The usual convention is to exit with 0

9 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

on correct termination, and with a meaningful (for the parent) error code on abort.

It is often the case that a parent process must coordinate its actions with those of its children,
maybe exchanging with them various kind of messages. UNIX defines several sophisticated
inter-process communication (IPC) mechanisms, the simplest of which is a parent's ability to
test the termination status of its children. A synchronization mechanism is provided via the wait()
system call, that allows a parent to sleep until one of its children exits, and then get its exit
status. This call actually comes in three flavors, one simply called
wait()
and common to all version of UNIX (that i know of), one called
waitpid()
, which is a POSIX extension, and one called
wait3()
, and it's a BSD extension.

Here's an example call to wait(): a program spawns two children, then waits for their completion
and behaves differently according to which one is finished. Try to compile and execute it (no
need to type: you can cut and paste from your web browser...).

#include <sys/types.h>

#include <sys/wait.h>

#include <stdio.h>

int main(int argc, char *argv[])

10 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

    pid_t whichone, first, second;

    int howmany;

    int status;

    

    if ((first=fork())==0) /* Parent spawns 1st child */

    {

        printf("Hiya, I am the first child, "

               "and my id is %dn",

               getpid()

               );

        sleep(10); /* Sleep 10 sec, then exit */

11 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

        exit(0);   

    }

    else if (first == -1)

    {

        perror("1st fork: something went bananasn");

        exit(1);

    }

    else if ((second=fork())==0) /* Parent spawns 2nd  child */

    {

        printf("Hiya, I am the second child, "

               "and my id is %dn",

               getpid()

12 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

               );

        sleep(15); /* Sleep 15 sec, then exit */

        exit(0);  

    }

    else if (second == -1)

    {

        perror("2nd fork: something went bananasn");

        exit(1);

    }

          

    printf("This is the parentn");

13 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

   

    howmany=0;

    while (howmany < 2) /* Wait twice */

    {

        whichone=wait(&status);

        howmany++;

       

        if (whichone==first)

           printf("First child exited ");

        else

           printf("Second child exited ");

   

14 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

        if ((status & 0xffff)==0)

           printf("correctlyn");

        else

           printf("uncorrectlyn");

    }

    return 0;

The first part of this example, up to the howmany=0 statement, contains nothing new: just make
sure you understand what the instruction flow is in the parent and in the children. The parent
then enters a loop waiting for the children's completion. The
wait()
system call blocks the caller process until one of its immediate children (not children's children,
or other siblings) terminates, and then returns the pid of the terminated process. The argument
to
wait()
is the address on an integer variable or the NULL pointer. If it's not NULL, the system writes 16
bits of status information about the terminated child in the low-order 16 bits of that variable.
Among these 16 bits, the
higher

15 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

8 bits contain the


lower
8 bits of the argument the child passed to
exit()
, while the
lower
8 bits are all zero if the process exited correctly, and contain error information if not (see the
wait(2)
man page for details). Hence, if a child exits with 0 all those 16 bits are zero. To reveal if this is
actually the case we test the bitwise AND expression
(status & 0xffff)
, which evaluates as an integer whose lower 16 bits are those of
status
, and the others are zero. If it evaluates to zero, everything went fine, otherwise some trouble
occurred. Try changing the argument passed to
exit()
in one of the children.

The Posix and BSD extensions to wait() are useful when a parent must not block waiting for
children, but still wants to know about the children's termination status values via the wait
mechanism. We'll treat only the Posix waitpid() call, and
you are referred to the man page for the BSD call.

The waitpid() call is declared as follows in the sys/wait.h header:

pid_t waitpid(pid_t pid, int *statptr, int options);

Here the meaning of the the return value and of the pointer to the status statptr is exactly the
same in wait().
However this call allows to specify which children should be waited for and how. Specifically, the
first argument
pid
specifies the process(es) that must be waited for. The relevant (for now) cases are:

- pid == -1: all children are waited for;


- pid > 0: it specifies the pid of a single child that should be waited for (an error occurs if that
process does not exist or is not one of the caller's children);

16 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

The relevant (for now) value for the third argument is a constant called WNOHANG, that causes
the function not to suspend the caller's execution if status is not immediately available for one of
the child processes. This allows to implement a loop in which the parent can do something
useful and periodically poll the children's status as well.

Process creation

When a process starts, the O/S has to build an entry for it in the process table. The process
state will be marked as ``runnable''.

Here is the Linux fork() routine:

/*

 *  Ok, this is the main fork-routine. It copies the system process

 * information (task[nr]) and sets up the necessary registers. It

 * also copies the data segment in its entirety.

 */

int do_fork(unsigned long clone_flags, unsigned long usp, struct pt_regs *regs)

17 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

    int nr;

    unsigned long new_stack;

    struct task_struct *p;

    if(!(p = (struct task_struct*)__get_free_page(GFP_KERNEL)))

        goto bad_fork;

    new_stack = get_free_page(GFP_KERNEL);

    if (!new_stack)

        goto bad_fork_free;

    nr = find_empty_process();

18 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

    if (nr < 0)

        goto bad_fork_free;

    *p = *current;

    if (p->exec_domain && p->exec_domain->use_count)

        (*p->exec_domain->use_count)++;

    if (p->binfmt && p->binfmt->use_count)

        (*p->binfmt->use_count)++;

    p->did_exec = 0;

    p->kernel_stack_page = new_stack;

19 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

    *(unsigned long *) p->kernel_stack_page = STACK_MAGIC;

    p->state = TASK_UNINTERRUPTIBLE;

    p->flags &= ~(PF_PTRACED|PF_TRACESYS);

    p->pid = last_pid;

    p->p_pptr = p->p_opptr = current;

    p->p_cptr = NULL;

    p->signal = 0;

    p->it_real_value = p->it_virt_value = p->it_prof_value = 0;

    p->it_real_incr = p->it_virt_incr = p->it_prof_incr = 0;

    p->leader = 0;      /* process leadership doesn't inherit */

    p->tty_old_pgrp = 0;

20 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

    p->utime = p->stime = 0;

    p->cutime = p->cstime = 0;

    p->start_time = jiffies;

    p->mm->swappable = 0;   /* don't try to swap it out before it's set up *

    task[nr] = p;

    SET_LINKS(p);

    nr_tasks++;

    /* copy all the process information */

    copy_thread(nr, clone_flags, usp, p, regs);

    if (copy_mm(clone_flags, p))

21 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

        goto bad_fork_cleanup;

    p->semundo = NULL;

    copy_files(clone_flags, p);

    copy_fs(clone_flags, p);

    /* ok, now we should be set up.. */

    p->mm->swappable = 1;

    p->exit_signal = clone_flags & CSIGNAL;

    p->counter = current->counter >> 1;

    p->state = TASK_RUNNING;    /* do this last, just in case */

    return p->pid;

22 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

bad_fork_cleanup:

    task[nr] = NULL;

    REMOVE_LINKS(p);

    nr_tasks--;

bad_fork_free:

    free_page(new_stack);

    free_page((long) p);

bad_fork:

    return -EAGAIN;

 Process suspension

23 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

wait

Suppose the file was a temporary file. After printing, it should be deleted. The child cannot
delete it, because after the ``exec'' it no longer exists. The parent cannot directly delete it
because the ``lpr'' program may still be accessing it. (Actually, an option on lpr allows removal
after printing - ignore this.) When ``lpr'' terminates, the parent can remove the file. So the parent
has to be able to decide when a child has finished.

#include

#include

pid_t wait(int *status)

This waits for a process to terminate and returns the PID of one that did. It also returns status
information.

if ((pid = fork()) == 0)

  if (execlp("lpr",

             "lpr",

             "myfile",

24 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

             (char *) 0) == -1)

    fprintf(stderr,

            "exec failedn");

else {

  /* do lots of things while

     the printing takes place

   */

  /* now remove the file */

  while (wait(NULL) != pid)

    ;

  unlink("myfile");

25 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

  /* and do lots more things */

sleep

A process may suspend for a period of time using the sleep command

unsigned int sleep(seconds)

Bovenkant formulier

Process removal

26 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

exit

When a process executes the ``exit'' command it terminates.

kill

One process can send simple messages to another using the ``kill'' command

#include

#include

int kill( pid_t pid, int sig);

The process to which the signal is sent must have the same uid or be a ``super user'' process.
The signal is often SIGINT or SIGKILL.

if (pid = (fork() == 0))

  ...

else {

27 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

  /* let the child run for

     10 seconds max

   */

  sleep(10);

  kill(pid, SIGINT);

signal

A process can catch certain signals by installing a signal handler which is a function invoked
when the signal arrives.

Bovenkant formulier

28 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

Onderkant formulier

Process environment

A process has an entry in a table of processes. This table contains all sorts of information (see
next lecture) including user ID, current directory, etc. A set of function calls allows access to
much of this information:

Process ID

#include

pid_t getpid(void);

User ID

#include

uid_t getuid(void);

29 / 30
Operating Systems (Chapter 4)

Written by Administrator
Sunday, 04 October 2009 10:12 - Last Updated Tuesday, 26 January 2010 17:56

User name

char *getlogin(void);

30 / 30

You might also like