System Level Design Languages:Concurrency in C Language
Concurrency in C Language
Since concurrency is an important concept in hardware design, and C/C++ is a sequential program, we need mechanisms for modeling concurrency in this language.
In multiprocess operating systems (like UNIX), two or more processes can be executed independently. These processes (or threads which are light-weighted processes) can communicate with each other by several predefined data structures which can be shared between multiple processes. There are several functions for handling processes and their communications in the C language.
Functions for handling processes include fork, exec, spawn, wait, and exit. In addition, the data structures developed for interprocess communication (IPC) include pipes, message queues, semaphores, shared memory, and sockets. The following presents brief descriptions of the above functions and data structures.
Functions for Process Handling
A process can be created from another running process. A process has a unique identification number called PID and can be terminated by calling the exit function inside it. There are several functions that can create other processes. A number of these functions are listed below:
• execl: This function means execute and leave. It uses a program name as its arguments, creates a new process, runs the program specified in its arguments, and returns when the specified program terminates.
• fork and spawn: The functionalities of these functions are similar. However, fork is more flexible than spawn in changing the environment of the newly created process. These functions create new processes and return integers indicating every newly created process identification (PID). The returned integer is the PID of the newly created process (known as the child process). As an example, consider a system with n hardware modules for which we need to model concurrency to check the exact behavior of the system. For this purpose, we can use fork or spawn functions for each system module, and execute the functionality of the modules in the newly created processes.
An example of the fork function is shown in Figure 86.44. This code has a CPU, a memory module, and an IO device which are working concurrently. The main function uses three fork functions with pid1, pid2, and pid3 identifiers. This causes main to copy itself three times. In each instance of main, a PID is checked and appropriate functions related to that PID is executed.
• wait: This function causes the parent process to wait for the termination of its children. Its return value is the terminated child PID if it is greater than zero, or −1 if an error occurs. As an example,
consider the parent process in Figure 86.44, which creates the three processes for CPU, IO device, and the memory module. This process which is shown in Figure 86.45 waits for the termination of each of these processes if necessary. Figure 86.45 shows a sample code that waits for the three processes, discussed above, to terminate.
There are also several other functions for process handling like vfork, clone, waitpid, kill, killpg, system, execv, mutex lock for threads, etc. For the description of these functions, refer to Ref. [5].
Interprocess Communication (IPC)
The discussion above illustrated the creating and handling of processes. These processes may need to exchange data between each other. Since a process has its own environment and memory segment, it cannot access variables belonging to other processes unless they follow specific protocols. In these protocols, there is a shared portion of memory (which the programmer can access implicitly or explicitly). Two or more processes can access this shared portion for reading and writing. In the following we have listed a number of these protocols with their implemented functions in C.
• Message queues: A message queue is a shared memory between two or more processes. One process can send a message to a message queue and the other can receive the message from it. All processes that use the same message queue should share a common key. A message queue must first be initialized (by the msgget function). Then the processes can send data to the message queue (by the msgsnd function) or receive data from it (by the msgrcv function). Sending and receiving messages can be performed in nonblocking or blocking modes. In nonblocking mode, the msgsnd and msgrcv functions only return a value that shows the message is sent or received. In blocking mode, the process that calls one of these functions will be suspended until the send or receive function is finished.
• Shared memory: As is obvious from its name, shared memory is a portion of memory that one process creates and another can access. Its creation is performed by the shmget function. shmctl gives each process proper access to a created shared memory. The shmctl function can be performed only by the shared memory creator. A shared portion can be attached or detached to the address space of a process. These are done by shmat and shmdt functions, respectively. If a shared portion is attached to the memory space of a process (under proper permissions), that process can read from or write to that memory. Therefore, two processes can communicate via this portion of memory.
• Pipe: A pipe is a structure, similar to a file pointer or a descriptor, and makes the output of a process the input of another. There are two ways of creating a pipe. One way is to use the popen
function which returns a FILE ∗. The two processes can use fprintf and fscanf functions to write
or to read from a pipe. The pclose function is used for closing a pipe. Another way of creating a
pipe is to use the pipe function that accepts an integer array with two elements. This function returns two file descriptors in this array. The first element is used for reading from a pipe, and the second element is used for writing to a pipe. This function returns 0 on success and −1 in the case of an error. The two processes can use this pipe by read and write functions. This pipe is closed by closing the two file descriptors (by the close function).
• Semaphore: Semaphore is a shared variable (simply an integer) that several processes can have access to. Each process waits for this variable to become 0. When it becomes 0, only one process can access this variable and increment it by 1. The process owning the semaphore is actually having access to a protected part of resources. When the process is finished with the resource, it releases the semaphore by decrementing it by 1. Then another process that was waiting on this semaphore will perform the same operations.
Semaphores are useful when several processes need to access a protected resource. They are even useful for several (m) processes that need to access a limited number of protected resources (n);
here, m > n. In this situation, each process must wait on the semaphore to become equal or less than n − 1. Semaphores are initialized with the semget function. Their characteristics and permissions can be controlled by the semctl function, while their operations are performed by the semop function. Another set of functions for semaphores include sem_open, sem_init, sem_close, sem_unlink, sem_destroy, set_getvalue, sem_wait, sem_trywait, and sem_post. These functions are defined in the semaphore.h header file. Similar to message queues, the operations on semaphores can be done in blocking or nonblocking modes.
• Socket: Sockets are the most useful IPC elements. A socket generates a point-to-point and bidirectional communication between two processes. Sockets can also be used to connect two processes on different systems. In this situation, the socket address space is called the internet domain. This domain uses the TCP/IP protocol. Socket types include stream sockets, datagram sockets, sequential packet sockets, and raw sockets. Note that only sockets of the same type can be connected to each other. Sockets are created by the socket function. An internet address is assigned to them by the bind function. Listen and connect functions are used for their connection. One is used for the server side and the other for the client side. Data transfer can be performed by read, write, send, and recv functions. Functions sendto, sendmsg, recvfrom, and recvmsg are used for data transfer in a datagram socket type. datagram sockets do not require a connection to be established.
All of the communication types and protocols described above can be used in hardware design. For example, a CPU and a memory model can communicate via a message queue, or a bus arbiter can be modeled with semaphores.
Comments
Post a Comment