Pages

Wednesday, June 8, 2011

shmget() - asking for a shared memory segment

Asking for a Shared Memory Segment - shmget()

The system call that requests a shared memory segment is shmget(). It is defined as follows:

shm_id = shmget(
               key_t     k,        /* the key for the segment         */
               int       size,     /* the size of the segment         */
               int       flag);    /* create/use flag                 */
In the above definition, k is of type key_t or IPC_PRIVATE. It is the numeric key to be assigned to the returned shared memory segment. size is the size of the requested shared memory. The purpose of flag is to specify the way that the shared memory will be used. For our purpose, only the following two values are important:
  1. IPC_CREAT | 0666 for a server (i.e., creating and granting read and write access to the server)
  2. 0666 for any client (i.e., granting read and write access to the client)
Note that due to Unix's tradition, IPC_CREAT is correct and IPC_CREATE is not!!!If shmget() can successfully get the requested shared memory, its function value is a non-negative integer, the shared memory ID; otherwise, the function value is negative. The following is a server example of requesting a private shared memory of four integers:

#include  <sys/types.h>
#include  <sys/ipc.h>
#include  <sys/shm.h>
#include  <stdio.h>
     .....
int       shm_id;        /* shared memory ID      */
     .....
shm_id = shmget(IPC_PRIVATE, 4*sizeof(int), IPC_CREAT | 0666);
if (shm_id < 0) {
     printf("shmget error\n");
     exit(1);
}

/* now the shared memory ID is stored in shm_id */
If a client wants to use a shared memory created with IPC_PRIVATE, it must be a child process of the server, created after the parent has obtained the shared memory, so that the private key value can be passed to the child when it is created. For a client, changing IPC_CREAT | 0666 to 0666 works fine. A warning to novice C programmers: don't change 0666 to 666. The leading 0 of an integer indicates that the integer is an octal number. Thus, 0666 is 110110110 in binary. If the leading zero is removed, the integer becomes six hundred sixty six with a binary representation 1111011010.
Server and clients can have a parent/client relationship or run as separate and unrelated processes. In the former case, if a shared memory is requested and attached prior to forking the child client process, then the server may want to use IPC_PRIVATE since the child receives an identical copy of the server's address space which includes the attached shared memory. However, if the server and clients are separate processes, using IPC_PRIVATE is unwise since the clients will not be able to request the same shared memory segment with a unique and unknown key.
Suppose process 1, a server, uses shmget() to request a shared memory segment successfully. That shared memory segment exists somewhere in the memory, but is not yet part of the address space of process 1 (shown with dashed line below). Similarly, if process 2 requests the same shared memory segment with the same key value, process 2 will be granted the right to use the shared memory segment; but it is not yet part of the address space of process 2. To make a requested shared memory segment part of the address space of a process, use shmat().


shmat() - attaching a shared memory segment to an address space

Attaching a Shared Memory Segment to an Address Space - shmat()

After a shared memory ID is returned, the next step is to attach it to the address space of a process. This is done with system call shmat(). The use of shmat() is as follows:

shm_ptr = shmat(
               int       shm_id,        /* shared memory ID    */
               char      *ptr,          /* a character pointer */
               int       flag);         /* access flag         */
System call shmat() accepts a shared memory ID, shm_id, and attaches the indicated shared memory to the program's address space. The returned value is a pointer of type (void *) to the attached shared memory. Thus, casting is usually necessary. If this call is unsuccessful, the return value is -1. Normally, the second parameter is NULL. If the flag is SHM_RDONLY, this shared memory is attached as a read-only memory; otherwise, it is readable and writable.
In the following server's program, it asks for and attaches a shared memory of four integers.

#include  <sys/types.h>
#include  <sys/ipc.h>
#include  <sys/shm.h>
#include  <stdio.h>

int       shm_id;
key_t     mem_key;
int       *shm_ptr;

mem_key = ftok(".", 'a');
shm_id = shmget(mem_key, 4*sizeof(int), IPC_CREAT | 0666);
if (shm_id < 0) {
     printf("*** shmget error (server) ***\n");
     exit(1);
}

shm_ptr = (int *) shmat(shm_id, NULL, 0);  /* attach */
if ((int) shm_ptr == -1) {
     printf("*** shmat error (server) ***\n");
     exit(1);
}
The following is the counterpart of a client.

#include  <sys/types.h>
#include  <sys/ipc.h>
#include  <sys/shm.h>
#include  <stdio.h>

int       shm_id;
key_t     mem_key;
int       *shm_ptr;

mem_key = ftok(".", 'a');
shm_id = shmget(mem_key, 4*sizeof(int), 0666);
if (shm_id < 0) {
     printf("*** shmget error (client) ***\n");
     exit(1);
}

shm_ptr = (int *) shmat(shm_id, NULL, 0);
if ((int) shm_ptr == -1) { /* attach */
     printf("*** shmat error (client) ***\n");
     exit(1);
}
Note that the above code assumes the server and client programs are in the current directory. In order for the client to run correctly, the server must be started first and the client can only be started after the server has successfully obtained the shared memory.
Suppose process 1 and process 2 have successfully attached the shared memory segment. This shared memory segment will be part of their address space, although the actual address could be different (i.e., the starting address of this shared memory segment in the address space of process 1 may be different from the starting address in the address space of process 2).


keys

Keys

Unix requires a key of type key_t defined in file sys/types.h for requesting resources such as shared memory segments, message queues and semaphores. A key is simply an integer of type key_t; however, you should not use int or long, since the length of a key is system dependent.
There are three different ways of using keys, namely:
  1. a specific integer value (e.g., 123456)
  2. a key generated with function ftok()
  3. a uniquely generated key using IPC_PRIVATE (i.e., a private key).
The first way is the easiest one; however, its use may be very risky since a process can access your resource as long as it uses the same key value to request that resource. The following example assigns 1234 to a key:

key_t     SomeKey;

SomeKey = 1234;
The ftok() function has the following prototype:

key_t  ftok(
            const char *path,      /* a path string       */
            int        id          /* an integer value    */
           );   
Function ftok() takes a character string that identifies a path and an integer (usually a character) value, and generates an integer of type key_t based on the first argument with the value of id in the most significant position. For example, if the generated integer is 35028A5D16 and the value of id is 'a' (ASCII value = 6116), then ftok() returns 61028A5D16. That is, 6116 replaces the first byte of 35028A5D16, generating 61028A5D16.Thus, as long as processes use the same arguments to call ftok(), the returned key value will always be the same. The most commonly used value for the first argument is ".", the current directory. If all related processes are stored in the same directory, the following call to ftok() will generate the same key value:

#include  <sys/types.h>
#include  <sys/ipc.h>

key_t     SomeKey;

SomeKey = ftok(".", 'x');
After obtaining a key value, it can be used in any place where a key is required. Moreover, the place where a key is required accepts a special parameter, IPC_PRIVATE. In this case, the system will generate a unique key and guarantee that no other process will have the same key. If a resource is requested with IPC_PRIVATE in a place where a key is required, that process will receive a unique key for that resource. Since that resource is identified with a unique key unknown to the outsiders, other processes will not be able to share that resource and, as a result, the requesting process is guaranteed that it owns and accesses that resource exclusively.

What is Shared memory

What is Shared Memory?

In the discussion of the fork() system call, we mentioned that a parent and its children have separate address spaces. While this would provide a more secured way of executing parent and children processes (because they will not interfere each other), they shared nothing and have no way to communicate with each other. A shared memory is an extra piece of memory that is attached to some address spaces for their owners to use. As a result, all of these processes share the same memory segment and have access to it. Consequently, race conditions may occur if memory accesses are not handled properly. The following figure shows two processes and their address spaces. The yellow rectangle is a shared memory attached to both address spaces and both process 1 and process 2 can have access to this shared memory as if the shared memory is part of its own address space. In some sense, the original address spaces is "extended" by attaching this shared memory.



Shared memory is a feature supported by UNIX System V, including Linux, SunOS and Solaris. One process must explicitly ask for an area, using a key, to be shared by other processes. This process will be called the server. All other processes, the clients, that know the shared area can access it. However, there is no protection to a shared memory and any process that knows it can access it freely. To protect a shared memory from being accessed at the same time by several processes, a synchronization protocol must be setup.


A shared memory segment is identified by a unique integer, the shared memory ID. The shared memory itself is described by a structure of type shmid_ds in header file sys/shm.h. To use this file, files sys/types.h and sys/ipc.h must be included. Therefore, your program should start with the following lines:


#include  <sys/types.h>
#include  <sys/ipc.h>
#include  <sys/shm.h>
A general scheme of using shared memory is the following:
  • For a server, it should be started before any client. The server should perform the following tasks:
    1. Ask for a shared memory with a memory key and memorize the returned shared memory ID. This is performed by system call shmget().
    2. Attach this shared memory to the server's address space with system call shmat().
    3. Initialize the shared memory, if necessary.
    4. Do something and wait for all clients' completion.
    5. Detach the shared memory with system call shmdt().
    6. Remove the shared memory with system call shmctl().
  • For the client part, the procedure is almost the same:
    1. Ask for a shared memory with the same memory key and memorize the returned shared memory ID.
    2. Attach this shared memory to the client's address space.
    3. Use the memory.
    4. Detach all shared memory segments, if necessary.
    5. Exit.
On the next few pages, we shall describe these system calls and their uses.

wait() System Call

The wait() System Call

The system call wait() is easy. This function blocks the calling process until one of its child processes exits or a signal is received. For our purpose, we shall ignore signals. wait() takes the address of an integer variable and returns the process ID of the completed process. Some flags that indicate the completion status of the child process are passed back with the integer pointer. One of the main purposes of wait() is to wait for completion of child processes.
The execution of wait() could have two possible situations.
  1. If there are at least one child processes running when the call to wait() is made, the caller will be blocked until one of its child processes exits. At that moment, the caller resumes its execution.
  2. If there is no child process running when the call to wait() is made, then this wait() has no effect at all. That is, it is as if no wait() is there.
Consider the following program. Click here to download a copy of this file fork-03.c.

#include  <stdio.h>
#include  <string.h>
#include  <sys/types.h>

#define   MAX_COUNT  200
#define   BUF_SIZE   100

void  ChildProcess(char [], char []);    /* child process prototype  */

void  main(void)
{
     pid_t   pid1, pid2, pid;
     int     status;
     int     i;
     char    buf[BUF_SIZE];

     printf("*** Parent is about to fork process 1 ***\n");
     if ((pid1 = fork()) < 0) {
          printf("Failed to fork process 1\n");
          exit(1);
     }
     else if (pid1 == 0) 
          ChildProcess("First", "   ");

     printf("*** Parent is about to fork process 2 ***\n");
     if ((pid2 = fork()) < 0) {
          printf("Failed to fork process 2\n");
          exit(1);
     }
     else if (pid2 == 0) 
          ChildProcess("Second", "      ");

     sprintf(buf, "*** Parent enters waiting status .....\n");
     write(1, buf, strlen(buf));
     pid = wait(&status);
     sprintf(buf, "*** Parent detects process %d was done ***\n", pid);
     write(1, buf, strlen(buf));
     pid = wait(&status);
     printf("*** Parent detects process %d is done ***\n", pid);
     printf("*** Parent exits ***\n");
     exit(0);
}

void  ChildProcess(char *number, char *space)
{
     pid_t  pid;
     int    i;
     char   buf[BUF_SIZE];

     pid = getpid();
     sprintf(buf, "%s%s child process starts (pid = %d)\n", 
             space, number, pid);
     write(1, buf, strlen(buf));
     for (i = 1; i <= MAX_COUNT; i++) {
          sprintf(buf, "%s%s child's output, value = %d\n", space, number, i); 
          write(1, buf, strlen(buf));
     }
     sprintf(buf, "%s%s child (pid = %d) is about to exit\n", 
             space, number, pid);
     write(1, buf, strlen(buf));     
     exit(0);
}
This program shows some typical process programming techniques. The main program creates two child processes to execute the same printing loop and display a message before exit. For the parent process (i.e., the main program), after creating two child processes, it enters the wait state by executing the system call wait(). Once a child exits, the parent starts execution and the ID of the terminated child process is returned in pid so that it can be printed. There are two child processes and thus two wait()s, one for each child process. In this example, we do not use the returned information in variable status.However, the parent does not have to wait immediately after creating all child processes. It may do some other tasks. The following is an example. Click here for this file fork-04.c.

#include  <stdio.h>
#include  <string.h>
#include  <sys/types.h>

#define   MAX_COUNT  200
#define   BUF_SIZE   100

void  ChildProcess(char [], char []);    /* child process prototype  */
void  ParentProcess(void);               /* parent process prototype */

void  main(void)
{
     pid_t   pid1, pid2, pid;
     int     status;
     int     i;
     char    buf[BUF_SIZE];

     printf("*** Parent is about to fork process 1 ***\n");
     if ((pid1 = fork()) < 0) {
          printf("Failed to fork process 1\n");
          exit(1);
     }
     else if (pid1 == 0) 
          ChildProcess("First", "   ");

     printf("*** Parent is about to fork process 2 ***\n");
     if ((pid2 = fork()) < 0) {
          printf("Failed to fork process 2\n");
          exit(1);
     }
     else if (pid2 == 0) 
          ChildProcess("Second", "      ");

     ParentProcess();
     sprintf(buf, "*** Parent enters waiting status .....\n");
     write(1, buf, strlen(buf));
     pid = wait(&status);
     sprintf(buf, "*** Parent detects process %d was done ***\n", pid);
     write(1, buf, strlen(buf));
     pid = wait(&status);
     printf("*** Parent detects process %d is done ***\n", pid);
     printf("*** Parent exits ***\n");
     exit(0);
}

#define  QUAD(x)  (x*x*x*x)

void  ParentProcess(void)
{
     int  a, b, c, d;
     int  abcd, a4b4c4d4;
     int  count = 0;
     char buf[BUF_SIZE];

     sprintf(buf, "Parent is about to compute the Armstrong numbers\n");
     write(1, buf, strlen(buf));
     for (a = 0; a <= 9; a++)
          for (b = 0; b <= 9; b++)
               for (c = 0; c <= 9; c++)
                    for (d = 0; d <= 9; d++) {
                         abcd     = a*1000 + b*100 + c*10 + d;
                         a4b4c4d4 = QUAD(a) + QUAD(b) + QUAD(c) + QUAD(d);
                         if (abcd == a4b4c4d4) {
                              sprintf(buf, "From parent: "
                                      "the %d Armstrong number is %d\n",
                                      ++count, abcd);
                              write(1, buf, strlen(buf));
                         }
                    }
     sprintf(buf, "From parent: there are %d Armstrong numbers\n", count);
     write(1, buf, strlen(buf));
}

void  ChildProcess(char *number, char *space)
{
     pid_t  pid;
     int    i;
     char   buf[BUF_SIZE];

     pid = getpid();
     sprintf(buf, "%s%s child process starts (pid = %d)\n", 
             space, number, pid);
     write(1, buf, strlen(buf));
     for (i = 1; i <= MAX_COUNT; i++) {
          sprintf(buf, "%s%s child's output, value = %d\n", 
                  space, number, i); 
          write(1, buf, strlen(buf));
     }
     sprintf(buf, "%s%s child (pid = %d) is about to exit\n", 
             space, number, pid);
     write(1, buf, strlen(buf));
     exit(0);
}
The main program creates two child processes. Both processes call function ChildProcess(). The main program, the parent process, calls function ParentProcess(). This function computes all Armstrong numbers in the range of 0 and 9999. An Armstrong number in the range of 0 and 9999 is an integer whose value is equal to the sum of its digits raised to the fourth power. After this, the parent enters the wait state, waiting for the completion of its child processes. Note that since we have two processes running concurrently, we have no way to predict which one will terminate first and hence waiting for a specific child process is a risky move. This is why we don't have a "specific" wait in all of the previous programs.Warning: Although theoretically you can create as many processes as you want, systems always have some limits. Therefore, always check to see if the returned value of fork() is negative and report the result. If this does happen, try to reduce the number of child processes, or re-organize your program.
If the returned pid is unimportant, we can treat function wait() as a procedure. The following code is a simple modification to the last few statements (in the main function) of the previous example.

sprintf(buf, "*** Parent enters waiting status .....\n");
write(1, buf, strlen(buf));
wait(&status);
sprintf(buf, "*** Parent detects a child process was done ***\n");
write(1, buf, strlen(buf));
wait(&status);
printf("*** Parent detects another child process was done ***\n");
printf("*** Parent exits ***\n");
Click here to download a copy of this modified program (file fork-05.c).

execvp() System call

Execute a Program: the execvp() System Call

The created child process does not have to run the same program as the parent process does. The exec type system calls allow a process to run any program files, which include a binary executable or a shell script. On this page, we only discuss one such system call: execvp(). The execvp() system call requires two arguments:
  1. The first argument is a character string that contains the name of a file to be executed.
  2. The second argument is a pointer to an array of character strings. More precisely, its type is char **, which is exactly identical to the argv array used in the main program:

    int  main(int argc, char **argv)
    
    Note that this argument must be terminated by a zero.
When execvp() is executed, the program file given by the first argument will be loaded into the caller's address space and over-write the program there. Then, the second argument will be provided to the program and starts the execution. As a result, once the specified program file starts its execution, the original program in the caller's address space is gone and is replaced by the new program.
execvp() returns a negative value if the execution fails (e.g., the request file does not exist).
The following is an example (in file shell.c). Click here to download a copy.

#include  <stdio.h>
#include  <sys/types.h>

void  parse(char *line, char **argv)
{
     while (*line != '\0') {       /* if not the end of line ....... */ 
          while (*line == ' ' || *line == '\t' || *line == '\n')
               *line++ = '\0';     /* replace white spaces with 0    */
          *argv++ = line;          /* save the argument position     */
          while (*line != '\0' && *line != ' ' && 
                 *line != '\t' && *line != '\n') 
               line++;             /* skip the argument until ...    */
     }
     *argv = '\0';                 /* mark the end of argument list  */
}

void  execute(char **argv)
{
     pid_t  pid;
     int    status;

     if ((pid = fork()) < 0) {     /* fork a child process           */
          printf("*** ERROR: forking child process failed\n");
          exit(1);
     }
     else if (pid == 0) {          /* for the child process:         */
          if (execvp(*argv, argv) < 0) {     /* execute the command  */
               printf("*** ERROR: exec failed\n");
               exit(1);
          }
     }
     else {                                  /* for the parent:      */
          while (wait(&status) != pid)       /* wait for completion  */
               ;
     }
}

void  main(void)
{
     char  line[1024];             /* the input line                 */
     char  *argv[64];              /* the command line argument      */

     while (1) {                   /* repeat until done ....         */
          printf("Shell -> ");     /*   display a prompt             */
          gets(line);              /*   read in the command line     */
          printf("\n");
          parse(line, argv);       /*   parse the line               */
          if (strcmp(argv[0], "exit") == 0)  /* is it an "exit"?     */
               exit(0);            /*   exit if it is                */
          execute(argv);           /* otherwise, execute the command */
     }
}
Function parse() takes an input line and returns a zero-terminated array of char pointers, each of which points to a zero-terminated character string. This function loops until a binary zero is found, which means the end of the input line line is reached. If the current character of line is not a binary zero, parse() skips all white spaces and replaces them with binary zeros so that a string is effectively terminated. Once parse() finds a non-white space, the address of that location is saved to the current position of argv and the index is advanced. Then, parse() skips all non-whtitespace characters. This process repeats until the end of string line is reached and at that moment argv is terminated with a zero.
For example, if the input line is a string as follows:

"cp  abc.CC   xyz.TT"
Function parse() will return array argv[] with the following content:

Function execute() takes array argv[], treats it as a command line arguments with the program name in argv[0], forks a child process, and executes the indicated program in that child process. While the child process is executing the command, the parent executes a wait(), waiting for the completion of the child. In this special case, the parent knows the child's process ID and therefore is able to wait a specific child to complete.
The main program is very simple. It prints out a command prompt, reads in a line, parses it using function parse(), and determines if the name is "exit". If it is "exit", use exit() to terminate the execution of this program; otherwise, the main uses execute() to execute the command.

Monday, June 6, 2011

香港網友揭穿64大騙局系列(3)


何謂「平反64」?
http://hk.myblog.yahoo.com/64-truth/article?mid=4
個人認為, 支聯會叫囂近20年「平反64」, 是法、理基礎並未足夠

在西方國家, 當政者從來也甚少會為反思自己的歷史錯誤反思、認錯、或道歉;  例如﹕英國不會為自己過去二百多年來的侵略殖民行為道歉, 美國也不會為當年派遣八國聯軍侵略中國進行反思, 亦不曾對攻略伊拉克道歉。

所謂平反一件歷史事件, 是一件非常重大的政治行為,  支聯會叫「平反六四」背後亦有一套政治邏輯﹐就是認為六四是「愛國民主運動」, 不是「暴亂」, 所以宣佈戒嚴的決定是錯的,  武力清場的決定是錯的﹐而武力清場的本質就是「鎮壓」﹐一些人更用上「屠殺」一字。

但是, 要將一件歷史事件定性, 每一件歷史事件爆發的發展、過程, 亦是不能被忽略的, 而平反過程也須要按法理程序進行, 並非能夠透過感情渲染、叫囂就能達到

因此, 首要條件是, 大家就須要分析64的「愛國民主」成份