C Systems Programming


Command Line Arguments

In the Unix environment, arguments can be passed to the main function of a C or C++ program much like parameters can be passed to other functions in C or C++. The arguments are passed as strings to the main function. The syntax for the main function prototype in this case is as follows:

        int main (int argc, char *argv[])
where argc is the number of command line arguments, including the command name, and argv[i] is a pointer to the ith argument which is represented as a character string.

Example
For a program named prog1 which is invoked with the command line:
prog1 cat 12 -1 dog
the value of argc is 5, the value of argv[0] is "prog1", the value of argv[1] is "cat", the value of argv[2] is "12", the value of argv[3] is "-1", and the value of argv[4] is "dog".

Note that all arguments are represented as character strings. If numbers are to be passed into main, they must be converted from strings inside main.

Creating Multiple Processes

Links to examples (some are the same as the ones below): example 1, example 2, example 3, example 4, example 5
A special type of process important in the Unix environment is the daemon. A daemon is a process that waits for a user to request some action, and then performs the request. As an example, there is an ftp daemon that waits until a user requests that a file be transferred and then performs the request. This implies that the daemon must always be ready to receive requests at the same time it is transferring files. To solve this problem, the daemon can create an identical process (a child process) to handle the file transfer while the original process (the parent process) continues to wait for requests. The fork system call is used by a process to spawn a process identical to itself.

Example 1: C version

    #include <stdio.h>
    #include <unistd.h>  /* contains fork prototype */
    int main(void) {
        printf("Hello World!\n");
        fork();
        printf("\tI am process %d.\n", getpid());
    }

Example 1: C++ version
    #include <iostream.h>
    #include <unistd.h>  /* contains fork prototype */
    int main(void) {
        cout << "Hello World!\n";
        fork();
        cout << "\tI am process " << getpid() << "\n";
    }

When this program is executed, it first prints Hello World!. When the fork is executed, an identical process called the child is created. Then both the parent and the child process begin execution at the next statement. Note the following:

Example 2 - C version

Each process prints a message identifying itself.

    #include <stdio.h>
    #include <unistd.h>  /* contains fork prototype */
    int main(void) {
        int pid;
        printf("Hello World!\n");
        pid = fork();
        if (pid == 0)
            printf("    I am the child process.\n");
        else
            printf("    I am the parent process.\n");
    }

Example 2 - C++ version

Each process prints a message identifying itself.

    #include <iostream.h>
    #include <unistd.h>  /* contains fork prototype */
    int main(void) {
        int pid;
        cout << "Hello World!\n";
        pid = fork();
        if (pid == 0)
            cout << "    I am the child process.\n";
        else
            cout << "    I am the parent process.\n";
    }

Example 3 - C version

Multiple forks:

    #include <stdio.h>
    #include <unistd.h>  /* contains fork prototype */
    int main(void) {
        fork();
        fork();
        fork();
        printf("Hello World from process %d!\n", getpid());
    }

Example 3 - C++ version

Multiple forks:

    #include <iostream.h>
    #include <unistd.h>  /* contains fork prototype */
    int main(void) {
        fork();
        fork();
        fork();
        cout << "Hello World from process " << getpid() << "!\n";
    }

If the child process must be guaranteed to execute before the parent continues, the wait system call is used. A call to this function causes the parent process to wait until one of its child processes exits. The wait call returns the process id of the child process, which gives the parent the ability to wait for a particular child process to finish.

Example 4 - C version
Guarantees the child process will print its message before the parent process.
    #incluee <stdio.h>
    #include <sys/wait.h> /* contains prototype for wait */
    int main(void) {
        int pid;
        int status;
        printf("Hello World!\n");
        pid = fork();

        if (pid == -1) {        /* check for error in fork */
            perror("bad fork");
            exit(1);
        }

        if (pid == 0)
            printf("    I am the child process.\n");
        else {
            wait(&status);  /* parent waits for child to finish */
            printf("    I am the parent process.\n");
        }
    }

Example 4 - C++ version
Guarantees the child process will print its message before the parent process.
    #include <iostream.h>
    #include <unistd.h>
    #include <sys/wait.h> /* contains prototype for wait */
    #include <stdio.h>    /* contains prototype for perror */
    int main(void) {
        int pid;
        int status;
        cout << "Hello World!\n";
        pid = fork();

        if (pid == -1) {        /* check for error in fork */
            perror("bad fork");
            exit(1);
        }

        if (pid == 0)
            cout << "    I am the child process.\n";
        else {
            wait(&status);  /* parent waits for child to finish */
            cout << "    I am the parent process.\n";
        }
    }

Finding out process identifiers

Creating a shared memory

Shared memory provides a way to let two or more processes communicate via a shared memory segment. In Unix a shared memory segment is created via the system call shmget. For example,

#include <sys/shm.h>
#include <errno.h>  
#include <stdio.h>  /* prototype for perror */ 
#define PERMS 0666
struct share_mem
{
   ...all of the data I want processes to share goes in here ...
}

main()
{
   int shmid;

   shmid = shmget((key_t) IPC_PRIVATE, sizeof(struct share_mem),
                  PERMS | IPC_CREAT);
   if (shmid < 0)
      perror("shared memory creation failed \n");

The call to shmget takes three arguments. The first argument specifies a key that identifies the shared memory region. The value for key can be any random number other than 0. The value IPC_PRIVATE indicates that a new, unused shared memory region is desired. The second argument indicates the minimum size in bytes of the desired shared memory. In this example, all shared memory is placed within a struct and then the sizeof function returns the size of the struct in bytes. The third argument specifies the permissions to be attached to the shared memory segment (Octal 666) and indicates that an ID for the segment is to be created using the specified key.

The shmget call returns a negative value if the request fails and returns the id of the segment if the request is successful.

After the shared memory segment is created, one or more processes can attach to the shared memory segment by making a call to shmat. The call takes as input the id returned by the call to shmget, the virtual address to which to attach the shared memory segment and other attachment flags. A virtual address of 0 indicates an appropriate virtual address is to be selected by the kernel. The call returns a pointer to the shared memory segment if the attach was successful and negative one, otherwise. Here is an example call:

struct share_mem * shareptr;
.
.
if ( (shareptr = (struct share_mem *) shmat(shmid, 0, 0)) == 
     (struct share_mem *) -1)
   perror("can't attach to shared memory\n");

If you are using fork to create multiple processes then the child process is attached to any shared memory segments the parent is attached to before the fork is executed. If you want a process and its children to be attached to a shared memory segment, a good strategy is for the parent to call shmget to create the shared memory and shmat to attach to the segment before making calls to fork.

Before your processes terminate you must have them detach from any shared memory it is attached to and when use of the shared memory is complete, some process much remove it. This is accomplished via the system calls shmdt and shmctl.

The system call shmdt takes as input a pointer to the shared segment and returns a negative number if the call fails. Here is an example call:

if (shmdt(shareptr) < 0)
   perror("child detach failed \n");

The system call shmctl (shared memory control) takes as input the id of the shared memory and a command. For our purposes, the third argument to the call is 0. The command IPC_RMID indicates that the shared memory region is to be deallocated. It returns of -1 if the call fails. Here is an example call:

if (shmctl(shmid, IPC_RMID, 0) < 0)
   perror("remove of shared memory failed\n");

Each process that is attached to a shared memory must call shmdt to detach from it. One of the processes (generally the one that created the shared memory) must call shmctl to deallocate the shared memory.

In the process of code development, it isn't unusal for someone to create some shared segments which are somehow not deleted. Fortunately, there is an easy way to delete these shared memory segments which are inadvertently left out there. The Unix command:

ipcs

displays the number of shared memory segments on the system and the id of the shared memory segment.

The Unix command:

ipcrm -m id

deletes the shared memory segment with the specified id.