Last week, the mentor assigned a small program to familiarize us with the programming method of shared memory in unix. The question is briefly described as follows:
Design a C/S structure program, read and write the program in a piece of shared memory, the server is responsible for writing data (each time write an array of length of 100 ), the client is responsible for reading data and outputting the elements of the 100 arrays separated by tabs to a file. Data read/write synchronization is completed without any locks or semaphores. (The client cannot read the data when the server assigns values to elements in the array ).
Solution:
First, an int variable is applied for the shared memory to synchronize data read and write without locking. It is used as a flag for data read and write permissions (0-idle, at this time the producer can write, 1-busy, neither of which can be accessed; 2-new data, readable by the consumer ); after the Server/Client reads the flag and determines that it has obtained the permission, it should first modify the flag to inform the other party that it wants to operate on the data. However, the other party waits.
When the two programs work properly, the first address of the shared memory displayed in the Server/Client process is different, because the operating system will allocate its own process space for each separately running program, the process space is represented by an address segment. Each process can only run and operate on the address space allocated to it, rather than cross-process address access (for security reasons ); the purpose of shared storage is to allow such processes to share data with each other. The kernel will open up a separate space (independent of any process) for the shared storage space ), the process that connects to the shared bucket maps the actual address of the shared space to its own address space. In this way, the process accesses the shared space just like it accesses its own space, because of this, the addresses of shared memory are different for each process (only the mapped addresses are displayed ). The above is an individual's understanding. For details, please refer to the materials for confirmation.
Actual results:
It can be seen that, without any buffer, every time the Server writes an array, it needs to wait for the Client to read it before writing it. In this way, neither program can run efficiently, idling, polling, and waiting take most of the time.
Improved by using a 1000-length cyclic queue to cache data and introducing two cursors. Without locking, the two programs can perform asynchronous read operations on a queue. By defining two array cursors (readIndex and writeIndex), the other one indicates the starting position of the data that the consumer can read, the start position of the data that can be written by the producer;
For the producer, the queue range that can be operated is [writeIndex, readIndex-1)
For consumers, the queue range that can be operated is [readIndex, writeIndex)
To facilitate the determination of the queue status, the maximum number of writable data packets generated by the producer is (queue length-1). When readIndex = writeIndex indicates that the queue is empty, (writeIndex + 1) % queue length = readIndex indicates that the queue is full. In other cases, the consumer can write to the queue;
And the consumer only needs to be in writeIndex! = ReadIndex.
Here, the consumer and producer must modify the cursor after the data operation is complete.
Comparison between method 2 and method 3:
Obviously, the latter is much more efficient than the former. It takes only 18 seconds for the queue method to access 1 million pieces of data (the user time is still 18 seconds, it should be time-consuming for file write operations), but after method 2 is optimized, it takes 4 seconds to access one thousand data records, and 6 minutes 40 seconds to access 100,000 data records.
Improved results:
It takes only 18 seconds to read 1 million data records, and the speed increases significantly.
In addition, performance optimization: the transfer of a program in time and space takes a lot of time. Taking the consumer as an example, the program will continue to poll the corresponding flag to check whether it is readable, however, if the producer has not yet produced data, this round robin is actually meaningless and time slice is used for the time being. At this time, the consumer can take the initiative to sleep for a while and wait for the other party to produce data to be read again; the same is true for producers. After adding sleep, we found that the performance of the program has improved a lot. In addition, custom caches such as String concatenation can reduce program efficiency.
Code:
* ********************** Myshare. h ************************
# Include <stdio. h>
# Include <sys/types. h>
# Include <sys/stat. h>
# Include <fcntl. h>
# Include <sys/ipc. h>
# Include <sys/shm. h>
# Include <memory. h>
# Include <signal. h>
# Include <string. h>
# Define SHM_MODE (SHM_R | SHM_W | IPC_CREAT)
# Define PATHNAME "/etc/ssh/sshd_config"
# Define CLASSID 1
# Define ITEMLEN 100
# Define QEUELEN 1000
# Define QUIT 1
Typedef struct _ stItem
{
Unsigned int items [ITEMLEN];
} StItem;
Typedef struct _ stctrl
{
Int quit;
Int windex;
Int rindex;
} StCtrl;
Typedef struct _ stCItem
{
StItem item [QEUELEN];
Int quit;
Int windex;
Int rindex;
} StCItem;
* ************ Producer. c ****************
# Include "myshare. h"
/* Custom keyboard signal processing function (ctrl + c, ctrl + \)*/
Static void sig_quit (int signo );
/* Flag. If the keyboard is interrupted, set it to 1. Otherwise, the value is 0 */
Int quit;
Int main (int argc, char * argv [])
{
Unsigned long int I;
Int idx;
/* ID of the shared storage identifier */
Int shmid;
/* Shared storage pointer */
StCItem * shmptr;
/* KEY used to generate the flag ID */
Key_t shmkey;
/* Use the ftok function to pull the production KEY */
If (shmkey = ftok (PATHNAME, CLASSID) =-1)
Return my_error_sys ("ftok error ");
/* Apply for shared storage based on the KEY to ensure that the server/client is mounted to the same space */
If (shmid = shmget (shmkey, sizeof (stCItem), SHM_MODE) <0)
Return my_error_sys ("shmget error ");
/* Connect to shared storage */
If (shmptr = shmat (shmid, 0, 0) = (void *)-1)
Return my_error_sys ("shmat error ");
/* Register the processing function and listen to the keyboard signal */
If (signal (SIGINT, sig_quit) = SIG_ERR)
Return my_error_sys ("signal error ");
If (signal (SIGQUIT, sig_quit) = SIG_ERR)
Return my_error_sys ("signal error ");
/* Initialize shared storage every time the server starts */
Memset (void *) shmptr, 0, sizeof (stCItem ));
I = 0;
Quit = 0;
Printf ("produce data start... \ n ");
While (! Quit &&! Shmptr-> quit)
{
If (shmptr-> windex + 1) % QEUELEN! = Shmptr-> rindex)
{
/* Production data if the queue has an empty slot */
For (idx = 0; idx <ITEMLEN; ++ idx)
(Shmptr-> item) [shmptr-> windex]. items) [idx] = I;
++ I;
/* Modify the cursor after writing data */
Shmptr-> windex = (shmptr-> windex + 1) % QEUELEN;
}
Else
/* Avoid idling and take the initiative to discard the time slice */
Usleep (1 );
}
/* Notify the other party to exit */
Shmptr-> quit = 1;
/* Clear the corresponding shared storage ID */
If (shmctl (shmid, IPC_RMID, 0) <0)
Return my_error_sys ("shmctrl data error ");
Printf ("produce datas I: % d \ n", I );
Printf ("produce data stop... \ n ");
Return 0;
}
Int my_error_sys (const char * pstr)
{
Printf (pstr );
Printf ("\ n ");
Return-1;
}
Static void sig_quit (int signo)
{
Quit = 1;
}
* Consumer. c ********************
# Include "myshare. h"
Static void sig_quit (int signo );
Int quit;
Int main (int argc, char * argv [])
{
Unsigned long int idx;
Unsigned long int I;
Unsigned long int datanum;
/* ID of the shared storage identifier */
Int shmid;
/* Shared storage pointer */
StCItem * shmptr;
/* KEY used to generate the flag ID */
Key_t shmkey;
FILE * fp;
Char * resultfile = "data.txt ";
/* Use parameters to control the amount of data read for testing */
If (argc! = 2)
Return my_error_sys ("usage: consumer <data number> ");
Sscanf (argv [1], "% d", & datanum );
If (fp = fopen (resultfile, "w") = NULL)
Return my_error_sys ("open file error ");
/* Use the ftok function to pull the production KEY */
If (shmkey = ftok (pathname, classid) =-1)
Return my_error_sys ("ftok error ");
/* Apply for shared storage based on the key to ensure that the server/client is mounted to the same space */
If (shmid = shmget (shmkey, sizeof (stcitem), shm_mode) <0)
Return my_error_sys ("shmget error ");
/* Connect to shared storage */
If (shmptr = shmat (shmid, 0, 0) = (void *)-1)
Return my_error_sys ("shmat error ");
/* Register the processing function and listen to the keyboard signal */
If (signal (SIGINT, sig_quit) = sig_err)
Return my_error_sys ("signal error ");
If (signal (SIGQUIT, sig_quit) = SIG_ERR)
Return my_error_sys ("signal error ");
Quit = 0;
I = 0;
Memset (void *) shmptr, 0, sizeof (stCItem ));
Printf ("start consume data... \ n ");
While (! Quit)
{
/* Consume data if the queue has data */
If (shmptr-> rindex! = Shmptr-> windex)
{
For (idx = 0; idx <ITEMLEN; ++ idx)
{
Fprintf (fp, "% u \ t", (shmptr-> item) [shmptr-> rindex]. items [idx]);
}
/* Use a linefeed to refresh the cache */
Fprintf (fp, "\ n ");
/* Modify the cursor after reading the data */
Shmptr-> rindex = (shmptr-> rindex + 1) % QEUELEN;
++ I;
If (I> = datanum)
Break;
}
Else
/* Avoid idling and take the initiative to discard the time slice */
Usleep (1 );
}
/* Notify the other party to exit */
Shmptr-> quit = 1;
Fclose (fp );
/* Clear the corresponding shared storage ID */
If (shmctl (shmid, IPC_RMID, 0) <0)
Return my_error_sys ("shmctl data error ");
Printf ("consumer datas I: % d \ n", I );
Printf ("quit consume... \ n ");
Return 0;
}
Int my_error_sys (const char * pstr)
{
Printf (pstr );
Printf ("\ n ");
Return-1;
}
Static void sig_quit (int signo)
{
Quit = 1;
}