β β β ο½ππ»βΊπ«Δπ¬πβ β β β
π¦Programming Technology-Process and Thread Programming by undercode :
t.me/UndercodeTesting
look at the relationship between processes and Mach tasks and threads in UNIX systems. In a UNIX system, a process includes an executable program and a series of resources, such as a file descriptor table and address space. In Mach, a task includes only a series of resources; threads process all executable code. A Mach task can have any number of threads associated with it, and each thread must be associated with a task. All threads related to a given task share the task's resources. In this way, a thread is a program counter, a stack and a series of registers. All data structures that need to be used are tasks. A process in a UNIX system corresponds to a task and a separate thread in Mach.
[Directory]
----------------------------------------------- ---------------------------------
Original pipeline It is
more complicated to use C language to create pipeline than to use pipeline under shell. If you want to use C language to create a simple pipeline, you can use the system call pipe (). It accepts a parameter, which is an array of two integers. If the system call is successful, this array will include the two file descriptors used by the pipeline. After creating a pipeline, the process will generally generate a new process.
You can create a bidirectional pipe by opening two pipes. But the file description needs to be set correctly in the child process. Pipe () must be called in the fork () system call, otherwise the child process will not inherit the file descriptor. When using a half-duplex pipeline, any associated process must share an associated ancestor process. Because the pipeline exists in the system kernel, any process that is not among the ancestors of the process that created the pipeline will not be able to address it. This is not the case in named pipes.
written by undercode
β β β ο½ππ»βΊπ«Δπ¬πβ β β β
π¦Programming Technology-Process and Thread Programming by undercode :
t.me/UndercodeTesting
look at the relationship between processes and Mach tasks and threads in UNIX systems. In a UNIX system, a process includes an executable program and a series of resources, such as a file descriptor table and address space. In Mach, a task includes only a series of resources; threads process all executable code. A Mach task can have any number of threads associated with it, and each thread must be associated with a task. All threads related to a given task share the task's resources. In this way, a thread is a program counter, a stack and a series of registers. All data structures that need to be used are tasks. A process in a UNIX system corresponds to a task and a separate thread in Mach.
[Directory]
----------------------------------------------- ---------------------------------
Original pipeline It is
more complicated to use C language to create pipeline than to use pipeline under shell. If you want to use C language to create a simple pipeline, you can use the system call pipe (). It accepts a parameter, which is an array of two integers. If the system call is successful, this array will include the two file descriptors used by the pipeline. After creating a pipeline, the process will generally generate a new process.
You can create a bidirectional pipe by opening two pipes. But the file description needs to be set correctly in the child process. Pipe () must be called in the fork () system call, otherwise the child process will not inherit the file descriptor. When using a half-duplex pipeline, any associated process must share an associated ancestor process. Because the pipeline exists in the system kernel, any process that is not among the ancestors of the process that created the pipeline will not be able to address it. This is not the case in named pipes.
written by undercode
β β β ο½ππ»βΊπ«Δπ¬πβ β β β
β β β ο½ππ»βΊπ«Δπ¬πβ β β β
π¦ MORE PROGRAMMING :
Directory]
----------------------------------------------- ---------------------------------
pipe ()
system call: pipe ();
prototype: intpipe (intfd [2 ]);
Return value: If the system call is successful, return 0
If the system call fails, return -1: errno = EMFILE (no free file descriptor)
EMFILE (system file table is full)
EFAULT (fd array is invalid)
Note fd [0 ] Is used to read the pipeline, and fd [1] is used to write the pipeline.
#include <stdio.h>
#include <unistd.h>
#include <sys / types.h>
main ()
{
intfd [2];
pipe (fd);
..
}
Once the pipeline is created, we can create one New child process:
#include <stdio.h>
#include <unistd.h>
#include <sys / types.h>
main ()
{
intfd [2];
pid_t childpid;
pipe (fd);
if ((childpid = fork ()) ==-1)
{
perror ("fork");
exit (1);
} ..
}
If the parent process wants to read data from the child process, then it should close fd1, and at the same time The child process closes fd0. Conversely, if the parent process wishes to send data to the child process, then it should close fd0 and the child process closes fd1. Because the file descriptor is shared between the parent process and the child process, we must promptly close the end of the unnecessary pipeline. From a technical point of view, if one end of the pipe is not closed properly, you will not get an EOF.
#include <stdio.h>
#include <unistd.h>
#include <sys / types.h>
main ()
{
intfd [2];
pid_t childpid;
pipe (fd);
if ((childpid = fork ()) = = -1)
{
perror ("fork");
exit (1);
}
if (childpid == 0)
{
/ * Child process closes up in put side of pipe * /
close (fd [0]);
}
else
{
/ * Parent process closes up out put put of side of pipe * /
close (fd [1]);
} ..
}
As mentioned before, once the pipeline is created, the file descriptor used by the pipeline is the same as the normal file The file descriptor is the same.
#include <stdio.h>
#include <unistd.h>
#include <sys / types.h>
intmain (void)
{
intfd [2], nbytes;
pid_tchildpid;
charstring [] = "Hello, world!";
charreadbuffer [ 80];
pipe (fd);
if ((childpid = fork ()) ==-1)
{
perror ("fork");
exit (1);
}
if (childpid == 0)
{
/ * Child process closes up in put side of pipe * /
close (fd [0]);
/ * Send "string" through the out put side of pipe * /
write (fd [1], string, strlen (string));
exit (0);
}
else
{
/ * Parent process closes up out put side of pipe * /
close (fd [1]);
/ * Readinastringfromthepipe * /
nbytes = read (fd [0], readbuffer, sizeof (readbuffer)) ;
printf ("Receivedstring:% s", readbuffer);
}
return (0);
} In
general, the file descriptor in the child process will be copied to the standard input and output. This way the child process can use exec () to execute another program, which inherits the standard data flow.
[Directory]
----------------------------------------------- ---------------------------------
dup ()
system call: dup ();
prototype: inddup (intoldfd);
Return: If the system call succeeds, a new file descriptor
is returned. If the system call fails, -1 is returned: errno = EBADF (oldfd is not a valid file descriptor)
EBADF (newfd is out of range)
EMFILE (process file descriptors too many)
Note that the old file descriptor oldfd is not closed. Although the old file descriptor and the newly created file descriptor can be used interchangeably, in general, you need to close one first. The system call dup () uses the free file descriptor with the smallest number.
π¦ MORE PROGRAMMING :
Directory]
----------------------------------------------- ---------------------------------
pipe ()
system call: pipe ();
prototype: intpipe (intfd [2 ]);
Return value: If the system call is successful, return 0
If the system call fails, return -1: errno = EMFILE (no free file descriptor)
EMFILE (system file table is full)
EFAULT (fd array is invalid)
Note fd [0 ] Is used to read the pipeline, and fd [1] is used to write the pipeline.
#include <stdio.h>
#include <unistd.h>
#include <sys / types.h>
main ()
{
intfd [2];
pipe (fd);
..
}
Once the pipeline is created, we can create one New child process:
#include <stdio.h>
#include <unistd.h>
#include <sys / types.h>
main ()
{
intfd [2];
pid_t childpid;
pipe (fd);
if ((childpid = fork ()) ==-1)
{
perror ("fork");
exit (1);
} ..
}
If the parent process wants to read data from the child process, then it should close fd1, and at the same time The child process closes fd0. Conversely, if the parent process wishes to send data to the child process, then it should close fd0 and the child process closes fd1. Because the file descriptor is shared between the parent process and the child process, we must promptly close the end of the unnecessary pipeline. From a technical point of view, if one end of the pipe is not closed properly, you will not get an EOF.
#include <stdio.h>
#include <unistd.h>
#include <sys / types.h>
main ()
{
intfd [2];
pid_t childpid;
pipe (fd);
if ((childpid = fork ()) = = -1)
{
perror ("fork");
exit (1);
}
if (childpid == 0)
{
/ * Child process closes up in put side of pipe * /
close (fd [0]);
}
else
{
/ * Parent process closes up out put put of side of pipe * /
close (fd [1]);
} ..
}
As mentioned before, once the pipeline is created, the file descriptor used by the pipeline is the same as the normal file The file descriptor is the same.
#include <stdio.h>
#include <unistd.h>
#include <sys / types.h>
intmain (void)
{
intfd [2], nbytes;
pid_tchildpid;
charstring [] = "Hello, world!";
charreadbuffer [ 80];
pipe (fd);
if ((childpid = fork ()) ==-1)
{
perror ("fork");
exit (1);
}
if (childpid == 0)
{
/ * Child process closes up in put side of pipe * /
close (fd [0]);
/ * Send "string" through the out put side of pipe * /
write (fd [1], string, strlen (string));
exit (0);
}
else
{
/ * Parent process closes up out put side of pipe * /
close (fd [1]);
/ * Readinastringfromthepipe * /
nbytes = read (fd [0], readbuffer, sizeof (readbuffer)) ;
printf ("Receivedstring:% s", readbuffer);
}
return (0);
} In
general, the file descriptor in the child process will be copied to the standard input and output. This way the child process can use exec () to execute another program, which inherits the standard data flow.
[Directory]
----------------------------------------------- ---------------------------------
dup ()
system call: dup ();
prototype: inddup (intoldfd);
Return: If the system call succeeds, a new file descriptor
is returned. If the system call fails, -1 is returned: errno = EBADF (oldfd is not a valid file descriptor)
EBADF (newfd is out of range)
EMFILE (process file descriptors too many)
Note that the old file descriptor oldfd is not closed. Although the old file descriptor and the newly created file descriptor can be used interchangeably, in general, you need to close one first. The system call dup () uses the free file descriptor with the smallest number.
Look at the following program again:
..
childpid = fork ();
if (childpid == 0)
{
/ * Close up standard input of the child * /
close (0);
/ * Dup licate the input side of pipe to stdin * /
DUP (FD [0]);
execlp ( "Sort", "Sort", NULL);
.
}
as the file descriptor 0 (stdin) is closed, the DUP () of the feed line descriptor copied to its standard Entering. This way we can call execlp () and use the sort program to overwrite the text segment of the child process. Because the newly created program inherits the standard input / output stream from its parent process, it actually inherits the input end of the pipe as its standard input end. Now, any data sent by the original parent process to the pipeline will be sent directly to the sort function.
[Directory]
----------------------------------------------- ---------------------------------
dup2 ()
system call: dup2 ();
prototype: inddup2 (intoldfd, intnewfd );
Return value: If the call is successful, return a new file descriptor
If the call fails, return -1: errno = EBADF (oldfd is not a valid file descriptor)
EBADF (newfd out of range)
EMFILE (process file descriptors too many)
Note that dup2 () will close the old file descriptor.
Using this system call, the close operation and file descriptor copy operation can be integrated into one system call. In addition, this system call ensures the automatic operation of the operation, which means that the operation cannot be interrupted by other signals. This operation will be completed before returning to the system kernel. If the previous system call dup () is used, the programmer has to perform a close () operation before that. Please see the following program:
..
childpid = fork ();
if (childpid == 0)
{
/ * Close stdin, dup licate the input side of pipe to stdin * /
dup2 (0, fd [0]);
execlp ( "sort", "sort", NULL);
..
}
[Directory]
--------------------------------- -----------------------------------------------
popen () And pclose ()
If you think the above method of creating and using pipes is too cumbersome, you can also use the following simple method:
library functions: popen () and pclose ();
prototype: FILE * popen (char * command, char * type);
Return value: If successful, return a new file stream.
If the process or pipeline cannot be created, NULL is returned.
This standard library function creates a half-duplex pipe by calling pipe () inside the system, then it creates a child process, starts a shell, and finally executes the command in the command parameter on the shell. The direction of the data flow in the pipeline is controlled by the second parameter type. This parameter can be r or w, representing read or write, respectively. It cannot be read and written at the same time. Under the linux system, the pipeline will be opened in the way represented by the first character in the parameter type. So, if you write rw in the parameter type, the pipe will be opened for reading.
Although the usage of this library function is simple, there are some disadvantages. For example, it loses some control over the system when using the system call pipe (). Despite this, because shell commands can be used directly, some wildcards and other extended symbols in the shell can be used in the command parameter.
Pipelines created using popen () must be closed using pclose (). In fact, popen / pclose is very similar to fopen () / fclose () in the standard file input / output stream.
Library function: pclose ();
Prototype: intpclose (FILE * stream);
Return value: Returns the state of the system call wait4 ().
If the stream is invalid, or the system call wait4 () fails, it returns -1.
Note that this library function waits for the pipeline process to finish, and then closes the file stream. The library function pclose () executes the wait4 () function on the process created with popen (). When it returns, it will destroy the pipeline and file system.
In the following example, a pipeline is opened with the sort command, and then a character array is sorted:
..
childpid = fork ();
if (childpid == 0)
{
/ * Close up standard input of the child * /
close (0);
/ * Dup licate the input side of pipe to stdin * /
DUP (FD [0]);
execlp ( "Sort", "Sort", NULL);
.
}
as the file descriptor 0 (stdin) is closed, the DUP () of the feed line descriptor copied to its standard Entering. This way we can call execlp () and use the sort program to overwrite the text segment of the child process. Because the newly created program inherits the standard input / output stream from its parent process, it actually inherits the input end of the pipe as its standard input end. Now, any data sent by the original parent process to the pipeline will be sent directly to the sort function.
[Directory]
----------------------------------------------- ---------------------------------
dup2 ()
system call: dup2 ();
prototype: inddup2 (intoldfd, intnewfd );
Return value: If the call is successful, return a new file descriptor
If the call fails, return -1: errno = EBADF (oldfd is not a valid file descriptor)
EBADF (newfd out of range)
EMFILE (process file descriptors too many)
Note that dup2 () will close the old file descriptor.
Using this system call, the close operation and file descriptor copy operation can be integrated into one system call. In addition, this system call ensures the automatic operation of the operation, which means that the operation cannot be interrupted by other signals. This operation will be completed before returning to the system kernel. If the previous system call dup () is used, the programmer has to perform a close () operation before that. Please see the following program:
..
childpid = fork ();
if (childpid == 0)
{
/ * Close stdin, dup licate the input side of pipe to stdin * /
dup2 (0, fd [0]);
execlp ( "sort", "sort", NULL);
..
}
[Directory]
--------------------------------- -----------------------------------------------
popen () And pclose ()
If you think the above method of creating and using pipes is too cumbersome, you can also use the following simple method:
library functions: popen () and pclose ();
prototype: FILE * popen (char * command, char * type);
Return value: If successful, return a new file stream.
If the process or pipeline cannot be created, NULL is returned.
This standard library function creates a half-duplex pipe by calling pipe () inside the system, then it creates a child process, starts a shell, and finally executes the command in the command parameter on the shell. The direction of the data flow in the pipeline is controlled by the second parameter type. This parameter can be r or w, representing read or write, respectively. It cannot be read and written at the same time. Under the linux system, the pipeline will be opened in the way represented by the first character in the parameter type. So, if you write rw in the parameter type, the pipe will be opened for reading.
Although the usage of this library function is simple, there are some disadvantages. For example, it loses some control over the system when using the system call pipe (). Despite this, because shell commands can be used directly, some wildcards and other extended symbols in the shell can be used in the command parameter.
Pipelines created using popen () must be closed using pclose (). In fact, popen / pclose is very similar to fopen () / fclose () in the standard file input / output stream.
Library function: pclose ();
Prototype: intpclose (FILE * stream);
Return value: Returns the state of the system call wait4 ().
If the stream is invalid, or the system call wait4 () fails, it returns -1.
Note that this library function waits for the pipeline process to finish, and then closes the file stream. The library function pclose () executes the wait4 () function on the process created with popen (). When it returns, it will destroy the pipeline and file system.
In the following example, a pipeline is opened with the sort command, and then a character array is sorted:
#include <stdio.h>
# defineMAXSTRS5
intmain (void)
{
intcntr;
FILE * pipe_fp;
char * strings [MAXSTRS] = {"echo", "bravo", "alpha",
"charlie", "delta"};
/ * Createonewaypipelinewithcalltopopen () * /
if (( pipe_fp = popen ("sort", "w")) == NULL)
{
perror ("popen");
exit (1);
}
/ * Processingloop * /
for (cntr = 0; cntr <MAXSTRS; cntr ++) {
fputs (strings [cntr], pipe_fp);
fputc ('', pipe_fp);
}
/ * Closethepipe * /
pclose (pipe_fp);
return (0);
}
Because popen () uses the shell to execute commands, all shell extensions and Wildcards can be used. In addition, it can also use redirection and output pipeline functions with popen (). Look at the following example again:
popen ("ls ~ scottb", "r");
The following program is another example using popen (), which opens two pipes (one for the ls command and the other for the
sort command):
#include <stdio.h>
intmain (void)
{
FILE * pipein_fp, * pipeout_fp;
charreadbuf [80];
/ * Createonewaypipelinewithcalltopopen () * /
if ((pipein_fp = popen ("ls", "r")) == NULL)
{
perror ("popen");
exit (1);
}
/ * Createonewaypipelinewithcalltopopen () * /
if ((pipeout_fp = popen ("sort", "w")) == NULL)
{
perror ("popen");
exit (1);
}
/ * Processingloop * /
while (fgets (readbuf, 80, pipein_fp))
fputs (readbuf, pipeout_fp);
/ * Closethepipes * /
pclose (pipein_fp);
pclose (pipeout_fp);
return (0);
}
Finally, let's look at another example using popen (). This program is used to create a pipeline between a command and a file:
#include <stdio.h>
intmain (intargc, char * argv [])
{
FILE * pipe_fp, * infile;
charreadbuf [80];
if (argc! = 3 ) {
fprintf (stderr, "USAGE: popen3 [command] [filename]");
exit (1);
}
/ * Open up input file * /
if ((infile = fopen (argv [2], "rt")) == NULL)
{
perror ("fopen");
exit (1);
}
/ * Create one way pipe line with call topopen () * /
if ((pipe_fp = popen (argv [1], "w")) = = NULL)
{
perror ("popen");
exit (1);
}
/ * Processingloop * /
do {
fgets (readbuf, 80, infile);
if (feof (infile)) break;
fputs (readbuf, pipe_fp);
} while (! feof (infile));
fclose (infile);
pclose (pipe_fp);
return (0);
}
The following is an example of using this program:
popen3sortpopen3.c
popen3catpopen3.c
popen3morepopen3.c
popen3catpopen3.c | grepmain
[directory]
------------------------------------------ --------------------------------------
Named pipes
Named pipes are basically the same as general pipes, but There are also some significant differences:
* Named pipes exist as a special device file in the file system.
* Data can be shared between processes of different ancestors through pipes.
* When the shared pipe process has performed all I / O operations, the named pipe will continue to be saved in the file system for later use.
A pipe must have both a reading process and a writing process. If a process attempts to write to a pipe that has no read process, the system kernel will generate a SIGPIPE signal. This is especially important when more than two processes use pipes at the same time.
[Directory]
----------------------------------------------- ---------------------------------
There are several ways to create a FIFO to create a named pipe. The first two methods can use the shell.
mknodMYFIFOp
mkfifoa = rwMYFIFO
The two names above perform the same operation, but there is one difference. The command mkfifo provides a way to directly change the access rights of the FIFO file after creation, and the command mknod needs to call the command chmod.
A physical file system can easily distinguish a FIFO file through the p indicator.
$ ls-lMYFIFO
prw-r--r--1rootroot0Dec1422: 15MYFIFO |
Please note the pipe symbol "|" after the file name.
We can use the system call mknod () to create a FIFO pipeline:
library function: mknod ();
prototype: intmknod (char * pathname, mode_tmode, dev_tdev);
return value: if successful, return 0
if failed, return -1: errno = EFAULT (invalid path name)
EACCES (no access permission)
ENAMETOOLONG (path name too long)
ENOENT (invalid path name)
ENOTDIR (invalid path name)
Let's look at an example of using C language to create a FIFO pipeline:
# defineMAXSTRS5
intmain (void)
{
intcntr;
FILE * pipe_fp;
char * strings [MAXSTRS] = {"echo", "bravo", "alpha",
"charlie", "delta"};
/ * Createonewaypipelinewithcalltopopen () * /
if (( pipe_fp = popen ("sort", "w")) == NULL)
{
perror ("popen");
exit (1);
}
/ * Processingloop * /
for (cntr = 0; cntr <MAXSTRS; cntr ++) {
fputs (strings [cntr], pipe_fp);
fputc ('', pipe_fp);
}
/ * Closethepipe * /
pclose (pipe_fp);
return (0);
}
Because popen () uses the shell to execute commands, all shell extensions and Wildcards can be used. In addition, it can also use redirection and output pipeline functions with popen (). Look at the following example again:
popen ("ls ~ scottb", "r");
The following program is another example using popen (), which opens two pipes (one for the ls command and the other for the
sort command):
#include <stdio.h>
intmain (void)
{
FILE * pipein_fp, * pipeout_fp;
charreadbuf [80];
/ * Createonewaypipelinewithcalltopopen () * /
if ((pipein_fp = popen ("ls", "r")) == NULL)
{
perror ("popen");
exit (1);
}
/ * Createonewaypipelinewithcalltopopen () * /
if ((pipeout_fp = popen ("sort", "w")) == NULL)
{
perror ("popen");
exit (1);
}
/ * Processingloop * /
while (fgets (readbuf, 80, pipein_fp))
fputs (readbuf, pipeout_fp);
/ * Closethepipes * /
pclose (pipein_fp);
pclose (pipeout_fp);
return (0);
}
Finally, let's look at another example using popen (). This program is used to create a pipeline between a command and a file:
#include <stdio.h>
intmain (intargc, char * argv [])
{
FILE * pipe_fp, * infile;
charreadbuf [80];
if (argc! = 3 ) {
fprintf (stderr, "USAGE: popen3 [command] [filename]");
exit (1);
}
/ * Open up input file * /
if ((infile = fopen (argv [2], "rt")) == NULL)
{
perror ("fopen");
exit (1);
}
/ * Create one way pipe line with call topopen () * /
if ((pipe_fp = popen (argv [1], "w")) = = NULL)
{
perror ("popen");
exit (1);
}
/ * Processingloop * /
do {
fgets (readbuf, 80, infile);
if (feof (infile)) break;
fputs (readbuf, pipe_fp);
} while (! feof (infile));
fclose (infile);
pclose (pipe_fp);
return (0);
}
The following is an example of using this program:
popen3sortpopen3.c
popen3catpopen3.c
popen3morepopen3.c
popen3catpopen3.c | grepmain
[directory]
------------------------------------------ --------------------------------------
Named pipes
Named pipes are basically the same as general pipes, but There are also some significant differences:
* Named pipes exist as a special device file in the file system.
* Data can be shared between processes of different ancestors through pipes.
* When the shared pipe process has performed all I / O operations, the named pipe will continue to be saved in the file system for later use.
A pipe must have both a reading process and a writing process. If a process attempts to write to a pipe that has no read process, the system kernel will generate a SIGPIPE signal. This is especially important when more than two processes use pipes at the same time.
[Directory]
----------------------------------------------- ---------------------------------
There are several ways to create a FIFO to create a named pipe. The first two methods can use the shell.
mknodMYFIFOp
mkfifoa = rwMYFIFO
The two names above perform the same operation, but there is one difference. The command mkfifo provides a way to directly change the access rights of the FIFO file after creation, and the command mknod needs to call the command chmod.
A physical file system can easily distinguish a FIFO file through the p indicator.
$ ls-lMYFIFO
prw-r--r--1rootroot0Dec1422: 15MYFIFO |
Please note the pipe symbol "|" after the file name.
We can use the system call mknod () to create a FIFO pipeline:
library function: mknod ();
prototype: intmknod (char * pathname, mode_tmode, dev_tdev);
return value: if successful, return 0
if failed, return -1: errno = EFAULT (invalid path name)
EACCES (no access permission)
ENAMETOOLONG (path name too long)
ENOENT (invalid path name)
ENOTDIR (invalid path name)
Let's look at an example of using C language to create a FIFO pipeline:
mknod ("/ tmp / MYFIFO ", S_IFIFO | 0666,0);
In this example, the file / tmp / MYFIFO is the FIFO file to be created. Its access authority is 0666. Access permissions
can also be modified using umask:
final_umask = requested_permissions & ~ original_umask
A commonly used method of using system call umask () is to temporarily clear the value of
umask : umask (0);
mknod ("/ tmp / MYFIFO", S_IFIFO | 0666,0);
In addition, mknod () The third parameter in can only be used when creating a device file. It includes the
major and minor device numbers of the device file .
}
}
[Directory]
--------------------------------------------- -----------------------------------
Operation FIFO
I / O operation on FIFO and I on normal pipeline / O operations are basically the same, with one major difference. The system call open is used to physically open a pipe. In a half-duplex pipeline, this is unnecessary. Because the pipeline is in the system kernel, not in a physical file system. In our example, we will use the pipeline like a file stream, that is, use fopen () to open the pipeline and fclose () to close it.
Look at the following simple service program process:
#include <stdio.h>
#include <stdlib.h>
#include <sys / stat.h>
#include <unistd.h>
#include <linux / stat.h>
# defineFIFO_FILE "MYFIFO"
{
FILE * fp;
charreadbuf [80];
/ * CreatetheFIFOifitdoesnotexist * /
umask (0);
mknod (FIFO_FILE, S_IFIFO | 0666,0);
while (1)
{
fp = fopen (FIFO_FILE, "r");
fgets (readbuf , 80, fp);
printf ("Receivedstring:% s", readbuf);
fclose (fp);
return (0);
Because the FIFO pipeline has a blocking function by default, you can run this program in the background:
$ fifoserver &
Let's take a look at the following simple client program:
#include <stdio.h>
#include <stdlib.h>
#defineFIFO_FILE "MYFIFO"
intmain (int argc, char * argv [])
{
FILE * fp;
if (argc! = 2) {
printf ("USAGE: fifoclient [string]");
exit (1);
}
if ((fp = fopen (FIFO_FILE, "w")) == NULL) {
perror ("fopen");
exit (1);
}
fputs (argv [1], fp);
fclose (fp);
return (0);
}
[directory]
------------- -------------------------------------------------- -----------------
Blocking FIFO
Under normal circumstances, there will be blocking on the FIFO pipeline. In other words, if a FIFO pipe is opened for reading, it will block until other processes open the pipe to write information. This process is also reversed. If you don't need a blocking function, you can set the O_NONBLOCK flag in the system call open (), which will cancel the default blocking function.
[Directory]
----------------------------------------------- ---------------------------------
message queue
in SystemV versions of UNIX, aT & T introduced three new forms of IPC Features (message queue, semaphore, and shared memory). But the BSD version of UNIX uses sockets as the main form of IPC. The Linux system supports both versions.
[Directory]
----------------------------------------------- ---------------------------------
msgget ()
system call msgget ()
If you want to create a new message queue, or want to access an existing message queue, you can use the system call msgget ().
System call: msgget ();
Prototype: intmsgget (key_t key, int msgflg);
Return value: If successful, return message queue identifier
If it fails, return -1: errno = EACCESS (permission not allowed)
EEXIST (queue already exists , Cannot be created)
EIDRM (the queue flag is deleted)
ENOENT (the queue does not exist)
ENOMEM (the memory is not enough when creating the queue)
ENOSPC (the maximum queue limit is exceeded
) The first parameter in the system call msgget () is the keyword value (usually Returned by ftok ()). Then this keyword value will be compared with other keyword values ββalready in the system kernel. At this time, the opening and access operations are related to the content of the parameter msgflg.
IPC_CREAT Create this queue if it is not in the kernel.
IPC_EXCL, when used with IPC_CREAT, fails if the queue already exists.
In this example, the file / tmp / MYFIFO is the FIFO file to be created. Its access authority is 0666. Access permissions
can also be modified using umask:
final_umask = requested_permissions & ~ original_umask
A commonly used method of using system call umask () is to temporarily clear the value of
umask : umask (0);
mknod ("/ tmp / MYFIFO", S_IFIFO | 0666,0);
In addition, mknod () The third parameter in can only be used when creating a device file. It includes the
major and minor device numbers of the device file .
}
}
[Directory]
--------------------------------------------- -----------------------------------
Operation FIFO
I / O operation on FIFO and I on normal pipeline / O operations are basically the same, with one major difference. The system call open is used to physically open a pipe. In a half-duplex pipeline, this is unnecessary. Because the pipeline is in the system kernel, not in a physical file system. In our example, we will use the pipeline like a file stream, that is, use fopen () to open the pipeline and fclose () to close it.
Look at the following simple service program process:
#include <stdio.h>
#include <stdlib.h>
#include <sys / stat.h>
#include <unistd.h>
#include <linux / stat.h>
# defineFIFO_FILE "MYFIFO"
{
FILE * fp;
charreadbuf [80];
/ * CreatetheFIFOifitdoesnotexist * /
umask (0);
mknod (FIFO_FILE, S_IFIFO | 0666,0);
while (1)
{
fp = fopen (FIFO_FILE, "r");
fgets (readbuf , 80, fp);
printf ("Receivedstring:% s", readbuf);
fclose (fp);
return (0);
Because the FIFO pipeline has a blocking function by default, you can run this program in the background:
$ fifoserver &
Let's take a look at the following simple client program:
#include <stdio.h>
#include <stdlib.h>
#defineFIFO_FILE "MYFIFO"
intmain (int argc, char * argv [])
{
FILE * fp;
if (argc! = 2) {
printf ("USAGE: fifoclient [string]");
exit (1);
}
if ((fp = fopen (FIFO_FILE, "w")) == NULL) {
perror ("fopen");
exit (1);
}
fputs (argv [1], fp);
fclose (fp);
return (0);
}
[directory]
------------- -------------------------------------------------- -----------------
Blocking FIFO
Under normal circumstances, there will be blocking on the FIFO pipeline. In other words, if a FIFO pipe is opened for reading, it will block until other processes open the pipe to write information. This process is also reversed. If you don't need a blocking function, you can set the O_NONBLOCK flag in the system call open (), which will cancel the default blocking function.
[Directory]
----------------------------------------------- ---------------------------------
message queue
in SystemV versions of UNIX, aT & T introduced three new forms of IPC Features (message queue, semaphore, and shared memory). But the BSD version of UNIX uses sockets as the main form of IPC. The Linux system supports both versions.
[Directory]
----------------------------------------------- ---------------------------------
msgget ()
system call msgget ()
If you want to create a new message queue, or want to access an existing message queue, you can use the system call msgget ().
System call: msgget ();
Prototype: intmsgget (key_t key, int msgflg);
Return value: If successful, return message queue identifier
If it fails, return -1: errno = EACCESS (permission not allowed)
EEXIST (queue already exists , Cannot be created)
EIDRM (the queue flag is deleted)
ENOENT (the queue does not exist)
ENOMEM (the memory is not enough when creating the queue)
ENOSPC (the maximum queue limit is exceeded
) The first parameter in the system call msgget () is the keyword value (usually Returned by ftok ()). Then this keyword value will be compared with other keyword values ββalready in the system kernel. At this time, the opening and access operations are related to the content of the parameter msgflg.
IPC_CREAT Create this queue if it is not in the kernel.
IPC_EXCL, when used with IPC_CREAT, fails if the queue already exists.
If IPC_CREAT is used alone, msgget () either returns the identifier of a newly created message queue or the identifier of a queue with the same key value. If IPC_EXCL and IPC_CREAT are used together, msgget () either creates a new message queue or returns a failure value of -1 if the queue already exists. IPC_EXCL is useless by itself.
Let's look at an example of opening and creating a message queue:
intopen_queue (key_t keyval)
{
intqid;
if ((qid = msgget (keyval, IPC_CREAT | 0660)) ==-1)
{
return (-1);
}
return (qid);
}
[directory]
-------------- -------------------------------------------------- ----------------
msgsnd ()
system call msgsnd ()
Once we get the queue identifier, we can perform the operation we want on the queue. If you want to send a message to the queue, you can use the system call msgsnd ():
system call: msgsnd ();
prototype: intmsgsnd (int msqid, struct msgbuf * msgp, int msgsz, int msgflg);
return value: if successful , 0.
If it fails, -1: errno = EAGAIN (the queue is full and IPC_NOWAIT is used)
EACCES (no write permission)
EFAULT (invalid msgp address)
EIDRM (the message queue has been deleted)
EINTR (when waiting for a write operation, a Signal)
EINVAL (invalid message queue identifier, non-positive message type, or
invalid message length)
ENOMEM (not enough memory to copy message buffer)
The first parameter of the system call msgsnd () is the message queue identifier, which is returned by the system call msgget. The second parameter is msgp, which is a pointer to the message buffer. The parameter msgsz contains the size of the message in bytes, but does not include the length of the message type (4 bytes).
The parameter msgflg can be set to 0 (this parameter is ignored at this time), or use IPC_NOWAIT.
If the message queue is full, the message will not be written to the message queue and control will return to the calling process. If not specified, the calling process will hang until the message can be written to the queue.
The following is a program to send messages:
intsend_message (int qid, struct mymsgbuf * qbuf)
{
intresult, length;
/ * The length is essentially the size of the structure minus sizeof (mtype) * /
length = sizeof (structmymsgbuf) -sizeof ( long);
if ((result = msgsnd (qid, qbuf, length, 0)) ==-1)
{
return (-1);
}
return (result);
}
This applet tries to store the buffer in qbuf The message is sent to the message queue qid. The following program is a complete program combining the above two programs:
#include <stdio.h>
#include <stdlib.h>
#include <linux / ipc.h>
#include <linux / msg.h>
main ()
{
intqid;
key_t msgkey;
struct mymsgbuf {
longmtype; / * Message type * /
intrequest; / * Work request number * /
doublesalary; / * Employee's salary * /
} msg;
/ * Generateour IPC key value * /
msgkey = ftok (".", 'M');
/ * Open / createthequeue * /
if ((qid = open_queue (msgkey)) ==-1) {
perror ("open_queue");
exit (1);
}
/ * Load up the message with ar bitrary test data * /
msg.mtype = 1; / * Messagetypemustbeapositivenumber! * /
msg.request = 1; / * Dataelement # 1 * /
msg.salary = 1000.00; / * Data element # 2 (my yearly salary!) * /
/ * Bombsaway! * /
If ((send_message (qid, &msg)) ==-1) {
perror ("send_message");
exit (1);
}
}
After creating and opening the message queue, we load the test data into the message buffer. Finally, send_messag is called to send the message to the message queue. Now that there is a message in the message queue, we can use the ipcs command to view the status of the queue. The following discusses how to get messages from the queue. You can use the system call msgrcv ():
[directory]
--------------------------------------- -----------------------------------------
msgrcv ()
Let's look at an example of opening and creating a message queue:
intopen_queue (key_t keyval)
{
intqid;
if ((qid = msgget (keyval, IPC_CREAT | 0660)) ==-1)
{
return (-1);
}
return (qid);
}
[directory]
-------------- -------------------------------------------------- ----------------
msgsnd ()
system call msgsnd ()
Once we get the queue identifier, we can perform the operation we want on the queue. If you want to send a message to the queue, you can use the system call msgsnd ():
system call: msgsnd ();
prototype: intmsgsnd (int msqid, struct msgbuf * msgp, int msgsz, int msgflg);
return value: if successful , 0.
If it fails, -1: errno = EAGAIN (the queue is full and IPC_NOWAIT is used)
EACCES (no write permission)
EFAULT (invalid msgp address)
EIDRM (the message queue has been deleted)
EINTR (when waiting for a write operation, a Signal)
EINVAL (invalid message queue identifier, non-positive message type, or
invalid message length)
ENOMEM (not enough memory to copy message buffer)
The first parameter of the system call msgsnd () is the message queue identifier, which is returned by the system call msgget. The second parameter is msgp, which is a pointer to the message buffer. The parameter msgsz contains the size of the message in bytes, but does not include the length of the message type (4 bytes).
The parameter msgflg can be set to 0 (this parameter is ignored at this time), or use IPC_NOWAIT.
If the message queue is full, the message will not be written to the message queue and control will return to the calling process. If not specified, the calling process will hang until the message can be written to the queue.
The following is a program to send messages:
intsend_message (int qid, struct mymsgbuf * qbuf)
{
intresult, length;
/ * The length is essentially the size of the structure minus sizeof (mtype) * /
length = sizeof (structmymsgbuf) -sizeof ( long);
if ((result = msgsnd (qid, qbuf, length, 0)) ==-1)
{
return (-1);
}
return (result);
}
This applet tries to store the buffer in qbuf The message is sent to the message queue qid. The following program is a complete program combining the above two programs:
#include <stdio.h>
#include <stdlib.h>
#include <linux / ipc.h>
#include <linux / msg.h>
main ()
{
intqid;
key_t msgkey;
struct mymsgbuf {
longmtype; / * Message type * /
intrequest; / * Work request number * /
doublesalary; / * Employee's salary * /
} msg;
/ * Generateour IPC key value * /
msgkey = ftok (".", 'M');
/ * Open / createthequeue * /
if ((qid = open_queue (msgkey)) ==-1) {
perror ("open_queue");
exit (1);
}
/ * Load up the message with ar bitrary test data * /
msg.mtype = 1; / * Messagetypemustbeapositivenumber! * /
msg.request = 1; / * Dataelement # 1 * /
msg.salary = 1000.00; / * Data element # 2 (my yearly salary!) * /
/ * Bombsaway! * /
If ((send_message (qid, &msg)) ==-1) {
perror ("send_message");
exit (1);
}
}
After creating and opening the message queue, we load the test data into the message buffer. Finally, send_messag is called to send the message to the message queue. Now that there is a message in the message queue, we can use the ipcs command to view the status of the queue. The following discusses how to get messages from the queue. You can use the system call msgrcv ():
[directory]
--------------------------------------- -----------------------------------------
msgrcv ()
system call: msgrcv () ;
Prototype: intmsgrcv (intmsqid, structmsgbuf * msgp, intmsgsz, longmtype, intmsgflg);
Return value: if successful, returns the number of bytes copied to the message buffer.
If it fails, return -1: errno = E2BIG (the length of the message is greater than msgsz, no MSG_NOERROR)
EACCES (no read permission)
EFAULT (the address pointed to by msgp is invalid)
EIDRM (the queue has been deleted)
EINTR (interrupted by the signal )
EINVAL (msgqid is invalid, or msgsz is less than 0)
ENOMSG (using IPC_NOWAIT, and the messages in the queue cannot meet the requirements)
Obviously, the first parameter is used to specify the queue that will read the message. The second parameter represents the address of the message buffer where the message is to be stored. The third parameter is the length of the message buffer, excluding the length of mtype, which can be calculated as follows:
msgsz = sizeof (structmymsgbuf) -sizeof (long); the
fourth parameter is to be read from the message queue The type of message. If the value of this parameter is 0, the longest message in the queue will be returned, regardless of its type.
If IPC_NOWAIT is used as a flag in the call, then when no data is available, the call will return ENOMSG to the calling process. Otherwise, the calling process will hang until a message in the queue meets the msgrcv () parameter requirements. If the queue is empty while the client is waiting for a message, EIDRM will be returned. If the process catches a signal while waiting for a message, it returns EINTR.
The following is a program to read messages from the queue:
intread_message (int qid, long type, struct mymsgbuf * qbuf)
{
intresult, length;
/ * The length is essentially the size of the structure minus sizeof (mtype) * /
length = sizeof (structmymsgbuf) -sizeof (long);
if ((result = msgrcv (qid, qbuf, length, type, 0)) ==-1)
{
return (-1);
}
return (result);
}
After successfully reading a message, the entry for this message in the queue will be deleted.
The MSG_NOERROR bit in the parameter msgflg provides an additional purpose. If the actual length of the message is greater than msgsz and MSG_NOERROR is used at the same time, the message will be truncated and only messages equal to the length of msgsz will be returned. Under normal circumstances, the system call msgrcv () will return -1, and this message will continue to be stored in the queue. We can use this feature to compile a program, use this program to view the status of the message queue, and see if the message that meets our conditions has arrived:
intpeek_message (int qid, long type)
{
intresult, length;
if ((result = msgrcv ( qid, NULL, 0, type, IPC_NOWAIT)) ==-1)
{
if (errno == E2BIG)
return (TRUE);
}
return (FALSE);
}
In the above program, we ignored the buffer address and length. In this way, the system call will fail. Nonetheless, we can check the returned E2BIG value, which shows that the qualified message does exist.
[Directory]
----------------------------------------------- ---------------------------------
msgctl ()
system call msgctl ()
Below we continue to discuss how to use a given Internal data structure of the message queue. We can use the system call msgctl () to control the operation of the message queue.
Prototype: intmsgrcv (intmsqid, structmsgbuf * msgp, intmsgsz, longmtype, intmsgflg);
Return value: if successful, returns the number of bytes copied to the message buffer.
If it fails, return -1: errno = E2BIG (the length of the message is greater than msgsz, no MSG_NOERROR)
EACCES (no read permission)
EFAULT (the address pointed to by msgp is invalid)
EIDRM (the queue has been deleted)
EINTR (interrupted by the signal )
EINVAL (msgqid is invalid, or msgsz is less than 0)
ENOMSG (using IPC_NOWAIT, and the messages in the queue cannot meet the requirements)
Obviously, the first parameter is used to specify the queue that will read the message. The second parameter represents the address of the message buffer where the message is to be stored. The third parameter is the length of the message buffer, excluding the length of mtype, which can be calculated as follows:
msgsz = sizeof (structmymsgbuf) -sizeof (long); the
fourth parameter is to be read from the message queue The type of message. If the value of this parameter is 0, the longest message in the queue will be returned, regardless of its type.
If IPC_NOWAIT is used as a flag in the call, then when no data is available, the call will return ENOMSG to the calling process. Otherwise, the calling process will hang until a message in the queue meets the msgrcv () parameter requirements. If the queue is empty while the client is waiting for a message, EIDRM will be returned. If the process catches a signal while waiting for a message, it returns EINTR.
The following is a program to read messages from the queue:
intread_message (int qid, long type, struct mymsgbuf * qbuf)
{
intresult, length;
/ * The length is essentially the size of the structure minus sizeof (mtype) * /
length = sizeof (structmymsgbuf) -sizeof (long);
if ((result = msgrcv (qid, qbuf, length, type, 0)) ==-1)
{
return (-1);
}
return (result);
}
After successfully reading a message, the entry for this message in the queue will be deleted.
The MSG_NOERROR bit in the parameter msgflg provides an additional purpose. If the actual length of the message is greater than msgsz and MSG_NOERROR is used at the same time, the message will be truncated and only messages equal to the length of msgsz will be returned. Under normal circumstances, the system call msgrcv () will return -1, and this message will continue to be stored in the queue. We can use this feature to compile a program, use this program to view the status of the message queue, and see if the message that meets our conditions has arrived:
intpeek_message (int qid, long type)
{
intresult, length;
if ((result = msgrcv ( qid, NULL, 0, type, IPC_NOWAIT)) ==-1)
{
if (errno == E2BIG)
return (TRUE);
}
return (FALSE);
}
In the above program, we ignored the buffer address and length. In this way, the system call will fail. Nonetheless, we can check the returned E2BIG value, which shows that the qualified message does exist.
[Directory]
----------------------------------------------- ---------------------------------
msgctl ()
system call msgctl ()
Below we continue to discuss how to use a given Internal data structure of the message queue. We can use the system call msgctl () to control the operation of the message queue.
System call: msgctl ();
Call prototype: int msgctl (int msgqid, int cmd, struct msqid_ds * buf);
return value: 0 if successful.
-1, if it fails: errno = EACCES (no read permission and cmd is IPC_STAT)
EFAULT (the address pointed to by buf is invalid)
EIDRM (the queue was deleted during the read)
EINVAL (msgqid is invalid, or msgsz is less than 0)
EPERM (IPC_SET Or the IPC_RMID command is used, but the calling program does not have write permission)
Let's take a look at several commands that can be used:
IPC_STAT
reads the data structure of the message queue msqid_ds and stores it in the address specified by buf.
IPC_SET
sets the value of the ipc_perm element in the data structure of the message queue msqid_ds. This value is taken from the buf parameter.
IPC_RMID
removes the message queue from the system kernel.
We discussed the data structure of the message queue (msqid_ds) earlier. An instance of this data structure is kept in the system kernel for each message queue in the system. By using the IPC_STAT command, we can get a copy of this data structure. The following program is the process of implementing this function:
int get_queue_ds (int qid, struct msgqid_ds * qbuf)
{
if (msgctl (qid, IPC_STAT, qbuf) == -1)
{
return (-1);
}
return (0);
}
If the internal buffer cannot be copied, the calling process will return -1. If the call is successful, it returns 0. The data structure in the message queue should be included in the buffer.
The only element that can be changed in the data structure in the message queue is ipc_perm. It includes the access rights of the queue and information about the creator and owner of the queue. You can change the user id, user group id, and access permissions for the message queue.
The following is a program to modify the queue access mode:
int change_queue_mode (int qid, char * mode)
{
struct msqid_ds tmpbuf;
/ * Retrieve a current copy of the internal data structure * /
get_queue_ds (qid, & tmpbuf);
/ * Change the permissions using an old trick * /
sscanf (mode, "% ho", & tmpbuf.msg_perm.mode);
/ * Update the internal data structure * /
if (msgctl (qid, IPC_SET, & tmpbuf) == -1)
{
return ( -1);
}
return (
}
We call get_queue_ds to read the internal data structure of the queue. Then we call sscanf () to modify the value of the mode member in the data structure msg_perm. But it was not until msgctl () was called that the permission change was actually completed. Here msgctl () uses the IPC_SET command.
Finally, we use the IPC_RMID command in the system call msgctl () to delete the message queue:
int remove_queue (int qid)
{
if (msgctl (qid, IPC_RMID, 0) == -1)
{
return (-1);
}
return (0 );
}
};
[Directory]
------------------------------------------ --------------------------------------
Semaphore
Call prototype: int msgctl (int msgqid, int cmd, struct msqid_ds * buf);
return value: 0 if successful.
-1, if it fails: errno = EACCES (no read permission and cmd is IPC_STAT)
EFAULT (the address pointed to by buf is invalid)
EIDRM (the queue was deleted during the read)
EINVAL (msgqid is invalid, or msgsz is less than 0)
EPERM (IPC_SET Or the IPC_RMID command is used, but the calling program does not have write permission)
Let's take a look at several commands that can be used:
IPC_STAT
reads the data structure of the message queue msqid_ds and stores it in the address specified by buf.
IPC_SET
sets the value of the ipc_perm element in the data structure of the message queue msqid_ds. This value is taken from the buf parameter.
IPC_RMID
removes the message queue from the system kernel.
We discussed the data structure of the message queue (msqid_ds) earlier. An instance of this data structure is kept in the system kernel for each message queue in the system. By using the IPC_STAT command, we can get a copy of this data structure. The following program is the process of implementing this function:
int get_queue_ds (int qid, struct msgqid_ds * qbuf)
{
if (msgctl (qid, IPC_STAT, qbuf) == -1)
{
return (-1);
}
return (0);
}
If the internal buffer cannot be copied, the calling process will return -1. If the call is successful, it returns 0. The data structure in the message queue should be included in the buffer.
The only element that can be changed in the data structure in the message queue is ipc_perm. It includes the access rights of the queue and information about the creator and owner of the queue. You can change the user id, user group id, and access permissions for the message queue.
The following is a program to modify the queue access mode:
int change_queue_mode (int qid, char * mode)
{
struct msqid_ds tmpbuf;
/ * Retrieve a current copy of the internal data structure * /
get_queue_ds (qid, & tmpbuf);
/ * Change the permissions using an old trick * /
sscanf (mode, "% ho", & tmpbuf.msg_perm.mode);
/ * Update the internal data structure * /
if (msgctl (qid, IPC_SET, & tmpbuf) == -1)
{
return ( -1);
}
return (
}
We call get_queue_ds to read the internal data structure of the queue. Then we call sscanf () to modify the value of the mode member in the data structure msg_perm. But it was not until msgctl () was called that the permission change was actually completed. Here msgctl () uses the IPC_SET command.
Finally, we use the IPC_RMID command in the system call msgctl () to delete the message queue:
int remove_queue (int qid)
{
if (msgctl (qid, IPC_RMID, 0) == -1)
{
return (-1);
}
return (0 );
}
};
[Directory]
------------------------------------------ --------------------------------------
Semaphore
Semaphore is one that can be used to control multiple Counter for processes accessing shared resources. It is often used as a locking mechanism to prevent another process from accessing the same resource when one process is accessing the shared resource. The following briefly introduces the data structure involved in the semaphore.
1. The data structure semid_ds in the kernel is the
same as the message queue. The system kernel saves an internal data structure for each semaphore set in the kernel address space. The prototype of the data structure is semid_ds. It is defined as follows in linux / sem.h:
/ * One semid data structure for each set of semaphores in the system. * /
structsemid_ds {
structipc_permsem_perm; / * permissions..seeipc.h * /
time_tsem_otime; / * last semop time * /
time_tsem_ctime; / * last change time * /
structsem * sem_base; / * ptr to first semaphore in array * /
structwait_queue
structwait_queue * eventz;
structsem_undo * undo; / * undo requestson this array * /
ushortsem_nsems; / * no. of semaphores in array * /
};
sem_perm is an instance of the data structure ipc_perm defined in linux / ipc.h. It holds the information about the access rights of the semaphore set and the information about the creator of the semaphore set.
sem_otime The last semop () operation time.
The last time sem_ctime changed this data structure.
sem_base Pointer to the first semaphore in the array.
The number of unfinished requests in the sem_undo array.
sem_nsems The number of semaphores in the semaphore set (array).
2. The data structure sem
in the kernel contains a pointer to the semaphore array in the data structure semid_ds. Each element in this array is a
Data structure sem. It is also defined in linux / sem.h:
/ * One semaphore structure for each semaphore in the system. * /
Structsem {
shortsempid; / * pid of las toperation * /
ushortsemval; / * current value * /
ushortsemncnt; / * num procs awaiting increase in semval * /
ushortsemzcnt; / * num procs awaiting semval = 0 * /
};
sem_pid PID of the last operation (process ID).
The current value of sem_semval semaphore.
sem_semncnt The number of processes waiting for resources.
sem_semzcnt The number of processes waiting for resources to be completely idle.
[Directory]
----------------------------------------------- ---------------------------------
semget ()
We can use the system call semget () to create a new semaphore Set, or access an existing semaphore set:
system call: semget ();
prototype: intsemget (key_t key, int nsems, int semflg);
Return value: If successful, the IPC identifier of the semaphore set is returned. If it fails, it returns -1: errno = EACCESS (no permission)
EEXIST (the semaphore set already exists and cannot be created)
EIDRM (the semaphore set has been deleted)
ENOENT (the semaphore set does not exist, and IPC_CREAT is not used)
ENOMEM (no Enough memory to create a new semaphore set) The first parameter of the
ENOSPC (over limit)
system call semget () is the key value (generally returned by the system call ftok ()). The system kernel compares this value with the key value of other semaphore sets present in the system. The opening and access operations are related to the content in the parameter semflg. IPC_CREAT If the semaphore set does not exist in the system kernel, create a semaphore set. When IPC_EXCL is used together with IPC_CREAT, if the semaphore set already exists, the call fails. If IPC_CREAT is used alone, semget () either returns the identifier of the newly created semaphore set or the identifier of the semaphore with the same key value already existing in the system. If IPC_EXCL and IPC_CREAT are used together, either the identifier of the newly created semaphore set is returned, or -1 is returned. IPC_EXCL is useless alone. The parameter nsems indicates the number of semaphores that should be created in a new semaphore set. The maximum number of semaphores in the semaphore set is defined in linux / sem.h:
1. The data structure semid_ds in the kernel is the
same as the message queue. The system kernel saves an internal data structure for each semaphore set in the kernel address space. The prototype of the data structure is semid_ds. It is defined as follows in linux / sem.h:
/ * One semid data structure for each set of semaphores in the system. * /
structsemid_ds {
structipc_permsem_perm; / * permissions..seeipc.h * /
time_tsem_otime; / * last semop time * /
time_tsem_ctime; / * last change time * /
structsem * sem_base; / * ptr to first semaphore in array * /
structwait_queue
structwait_queue * eventz;
structsem_undo * undo; / * undo requestson this array * /
ushortsem_nsems; / * no. of semaphores in array * /
};
sem_perm is an instance of the data structure ipc_perm defined in linux / ipc.h. It holds the information about the access rights of the semaphore set and the information about the creator of the semaphore set.
sem_otime The last semop () operation time.
The last time sem_ctime changed this data structure.
sem_base Pointer to the first semaphore in the array.
The number of unfinished requests in the sem_undo array.
sem_nsems The number of semaphores in the semaphore set (array).
2. The data structure sem
in the kernel contains a pointer to the semaphore array in the data structure semid_ds. Each element in this array is a
Data structure sem. It is also defined in linux / sem.h:
/ * One semaphore structure for each semaphore in the system. * /
Structsem {
shortsempid; / * pid of las toperation * /
ushortsemval; / * current value * /
ushortsemncnt; / * num procs awaiting increase in semval * /
ushortsemzcnt; / * num procs awaiting semval = 0 * /
};
sem_pid PID of the last operation (process ID).
The current value of sem_semval semaphore.
sem_semncnt The number of processes waiting for resources.
sem_semzcnt The number of processes waiting for resources to be completely idle.
[Directory]
----------------------------------------------- ---------------------------------
semget ()
We can use the system call semget () to create a new semaphore Set, or access an existing semaphore set:
system call: semget ();
prototype: intsemget (key_t key, int nsems, int semflg);
Return value: If successful, the IPC identifier of the semaphore set is returned. If it fails, it returns -1: errno = EACCESS (no permission)
EEXIST (the semaphore set already exists and cannot be created)
EIDRM (the semaphore set has been deleted)
ENOENT (the semaphore set does not exist, and IPC_CREAT is not used)
ENOMEM (no Enough memory to create a new semaphore set) The first parameter of the
ENOSPC (over limit)
system call semget () is the key value (generally returned by the system call ftok ()). The system kernel compares this value with the key value of other semaphore sets present in the system. The opening and access operations are related to the content in the parameter semflg. IPC_CREAT If the semaphore set does not exist in the system kernel, create a semaphore set. When IPC_EXCL is used together with IPC_CREAT, if the semaphore set already exists, the call fails. If IPC_CREAT is used alone, semget () either returns the identifier of the newly created semaphore set or the identifier of the semaphore with the same key value already existing in the system. If IPC_EXCL and IPC_CREAT are used together, either the identifier of the newly created semaphore set is returned, or -1 is returned. IPC_EXCL is useless alone. The parameter nsems indicates the number of semaphores that should be created in a new semaphore set. The maximum number of semaphores in the semaphore set is defined in linux / sem.h:
# defineSEMMSL32 / * <= 512maxnumofsemaphoresperid * /
The following is a program to open and create a semaphore set:
intopen_semaphore_set (key_t keyval, int numsems)
{
intsid;
if (! numsems)
return (-1);
if ((sid = semget (mykey, numsems, IPC_CREAT | 0660)) ==-1)
{
return (-1);
}
return (sid);
}
};
[directory]
---- -------------------------------------------------- --------------------------
semop ()
system call: semop ();
call prototype: int semop (int semid, struct sembuf * sops, unsign ednsops);
Return value: 0, if successful. -1, if it fails: errno = E2BIG (nsops is greater than the maximum number of ops)
EACCESS (insufficient authority)
EAGAIN (IPC_NOWAIT is used, but the operation cannot continue)
EFAULT (the address pointed to by sops is invalid)
EIDRM (the semaphore set has been deleted)
EINTR (received other signals while sleeping)
EINVAL (semaphore set does not exist, or semid is invalid)
ENOMEM (SEM_UNDO is used, but there is not enough memory to create the required data structure)
ERANGE (semaphore value is out of range)
The first parameter is the key value. The second parameter is a pointer to the array to be operated on. The third parameter is the number of operations in the array. The parameter sops points to an array consisting of sembuf. This array is defined in linux / sem.h:
/ * semop systemcall takes an array of these * /
structsembuf {
ushortsem_num; / * semaphore index in array * /
shortsem_op; / * semaphore operation * /
shortsem_flg; / * operation flags * / The number of
semaphores to be processed by sem_num.
The operation to be performed by sem_op.
sem_flg operation flag.
If sem_op is negative, then the semaphore will subtract its value. This is related to the resources controlled by the semaphore. If IPC_NOWAIT is not used, the calling process will go to sleep until the resources controlled by the semaphore are available. If sem_op is a positive number, the semaphore is added to its value. This is the resource that the process releases the semaphore control. Finally, if sem_op is 0, then the calling process will call sleep () until the semaphore value is 0. This is used when a process is waiting for completely idle resources.
[Directory]
----------------------------------------------- ---------------------------------
semctl ()
system call: semctl ();
prototype: int semctl (int semid , int semnum, int cmd, union semunarg);
Return value: If successful, it is a positive number.
If it fails, it is -1: errno = EACCESS (insufficient authority)
EFAULT (the address pointed to by arg is invalid)
EIDRM (the semaphore set has been deleted)
EINVAL (the semaphore set does not exist, or the semid is invalid)
EPERM (EUID does not have the right to cmd )
ERANGE (semaphore value out of range)
system call semctl is used to perform control operations on the semaphore set. This is very similar to the system call msgctl in the message queue. But the parameters of these two system calls are slightly different. Because the semaphore is generally used as a semaphore set, rather than a single semaphore. Therefore, in the operation of the semaphore set, not only the IPC key value but also the specific semaphore in the semaphore set must be known. Both of these system calls use the parameter cmd, which is used to indicate the specific command to be operated. The last parameter in the two system calls is also different. In the system call msgctl, the last parameter is a pointer to the data structure used in the kernel. We use this data structure to obtain some information about the message queue, and to set or change the access rights and users of the queue. However, additional optional commands are supported in the semaphore, which requires a more complex data structure.
The first parameter of the system call semctl () is the key value. The second parameter is the number of semaphores.
The following is a program to open and create a semaphore set:
intopen_semaphore_set (key_t keyval, int numsems)
{
intsid;
if (! numsems)
return (-1);
if ((sid = semget (mykey, numsems, IPC_CREAT | 0660)) ==-1)
{
return (-1);
}
return (sid);
}
};
[directory]
---- -------------------------------------------------- --------------------------
semop ()
system call: semop ();
call prototype: int semop (int semid, struct sembuf * sops, unsign ednsops);
Return value: 0, if successful. -1, if it fails: errno = E2BIG (nsops is greater than the maximum number of ops)
EACCESS (insufficient authority)
EAGAIN (IPC_NOWAIT is used, but the operation cannot continue)
EFAULT (the address pointed to by sops is invalid)
EIDRM (the semaphore set has been deleted)
EINTR (received other signals while sleeping)
EINVAL (semaphore set does not exist, or semid is invalid)
ENOMEM (SEM_UNDO is used, but there is not enough memory to create the required data structure)
ERANGE (semaphore value is out of range)
The first parameter is the key value. The second parameter is a pointer to the array to be operated on. The third parameter is the number of operations in the array. The parameter sops points to an array consisting of sembuf. This array is defined in linux / sem.h:
/ * semop systemcall takes an array of these * /
structsembuf {
ushortsem_num; / * semaphore index in array * /
shortsem_op; / * semaphore operation * /
shortsem_flg; / * operation flags * / The number of
semaphores to be processed by sem_num.
The operation to be performed by sem_op.
sem_flg operation flag.
If sem_op is negative, then the semaphore will subtract its value. This is related to the resources controlled by the semaphore. If IPC_NOWAIT is not used, the calling process will go to sleep until the resources controlled by the semaphore are available. If sem_op is a positive number, the semaphore is added to its value. This is the resource that the process releases the semaphore control. Finally, if sem_op is 0, then the calling process will call sleep () until the semaphore value is 0. This is used when a process is waiting for completely idle resources.
[Directory]
----------------------------------------------- ---------------------------------
semctl ()
system call: semctl ();
prototype: int semctl (int semid , int semnum, int cmd, union semunarg);
Return value: If successful, it is a positive number.
If it fails, it is -1: errno = EACCESS (insufficient authority)
EFAULT (the address pointed to by arg is invalid)
EIDRM (the semaphore set has been deleted)
EINVAL (the semaphore set does not exist, or the semid is invalid)
EPERM (EUID does not have the right to cmd )
ERANGE (semaphore value out of range)
system call semctl is used to perform control operations on the semaphore set. This is very similar to the system call msgctl in the message queue. But the parameters of these two system calls are slightly different. Because the semaphore is generally used as a semaphore set, rather than a single semaphore. Therefore, in the operation of the semaphore set, not only the IPC key value but also the specific semaphore in the semaphore set must be known. Both of these system calls use the parameter cmd, which is used to indicate the specific command to be operated. The last parameter in the two system calls is also different. In the system call msgctl, the last parameter is a pointer to the data structure used in the kernel. We use this data structure to obtain some information about the message queue, and to set or change the access rights and users of the queue. However, additional optional commands are supported in the semaphore, which requires a more complex data structure.
The first parameter of the system call semctl () is the key value. The second parameter is the number of semaphores.
The commands that can be used in the parameter cmd are as follows:
IPC_STAT reads the data structure semid_ds of a semaphore set and stores it in the buf parameter in semun.
IPC_SET sets the element ipc_perm in the data structure semid_ds of the semaphore set, and its value is taken from the buf parameter in semun.
Β· IPC_RMID deletes the semaphore set from memory.
Β· GETALL is used to read the value of all semaphores in the semaphore set.
Β· GETNCNT returns the number of processes waiting for resources.
Β· GETPID returns the PID of the last process that performed the semop operation.
GETVAL returns the value of a single semaphore in the semaphore set.
Β· GETZCNT returns the number of processes that are waiting for completely idle resources.
Β· SETALL sets the value of all semaphores in the semaphore set.
Β· SETVAL sets the value of a single semaphore in the semaphore set.
The parameter arg represents an instance of semun. semun is defined in linux / sem.h:
/ * arg for semctl systemcalls. * /
unionsemun {
intval; / * value for SETVAL * /
structsemid_ds * buf; / * buffer for IPC_STAT & IPC_SET * /
ushort * array; / * array for GETALL & SETALL * /
structseminfo * buf; / * buffer for IPC_INFO * /
void * pad;
val Used when executing the SETVAL command. buf is used in the IPC_STAT / IPC_SET command. Represents the data structure of the semaphore used in the kernel. The pointer used by array when using the GETALL / SETALL command.
The following program returns the value of the semaphore. When using the GETVAL command, the last parameter in the call is ignored:
intget_sem_val (intsid, intsemnum)
{
return (semctl (sid, semnum, GETVAL, 0));
}
The following is a practical example:
# defineMAX_PRINTERS5
printer_usage ()
{
int x;
for (x = 0; x <MAX_PRINTERS; x ++)
printf ("Printer% d :% d ", x, get_sem_val (sid, x));
}
The following procedure can be used to initialize a new semaphore value:
void init_semaphore (int sid, int semnum, int initval)
{
union semunsemopts;
semopts.val = initval;
semctl (sid, semnum, SETVAL, semopts);
}
Note that the last parameter in the system call semctl is a copy of the union type, not a pointer to the union type.
[Directory]
----------------------------------------------- ---------------------------------
Shared memory
Shared memory is a memory area shared by several processes. This can be said to be the fastest form of IPC because it does not require any intermediate operations (eg, pipes, message queues, etc.). It simply maps the memory segment directly into the address space of the calling process. Such a memory segment can be created by a process, and then other processes can read and write this memory segment.
The shared memory segment of each system also maintains an internal data structure shmid_ds in the system kernel. This data structure is defined in linux / shm.h as follows:
/ * One shmid data structure for each shared memory segment in the system. * /
Struct shmid_ds {
struct ipc_perm shm_perm; / * operation perms * /
int shm_segsz ; / * size of segment (bytes) * /
time_t shm_atime; / * last attach time * /
time_t shm_dtime; / * last detach time * /
time_t shm_ctime; / * last change time * /
unsigned short shm_cpid; / * pid of creator * /
unsigned short shm_lpid; / * pid of last operator * /
short shm_nattch; / * no. of current attaches * /
/ * the following are private * /
unsigned short shm_npages; / * size of segment (pages) * /
unsigned long * shm_pages; / * array of ptrs to frames-> SHMMAX * /
struct vm_area_struct * attaches; / * descriptors for attaches * /
};
IPC_STAT reads the data structure semid_ds of a semaphore set and stores it in the buf parameter in semun.
IPC_SET sets the element ipc_perm in the data structure semid_ds of the semaphore set, and its value is taken from the buf parameter in semun.
Β· IPC_RMID deletes the semaphore set from memory.
Β· GETALL is used to read the value of all semaphores in the semaphore set.
Β· GETNCNT returns the number of processes waiting for resources.
Β· GETPID returns the PID of the last process that performed the semop operation.
GETVAL returns the value of a single semaphore in the semaphore set.
Β· GETZCNT returns the number of processes that are waiting for completely idle resources.
Β· SETALL sets the value of all semaphores in the semaphore set.
Β· SETVAL sets the value of a single semaphore in the semaphore set.
The parameter arg represents an instance of semun. semun is defined in linux / sem.h:
/ * arg for semctl systemcalls. * /
unionsemun {
intval; / * value for SETVAL * /
structsemid_ds * buf; / * buffer for IPC_STAT & IPC_SET * /
ushort * array; / * array for GETALL & SETALL * /
structseminfo * buf; / * buffer for IPC_INFO * /
void * pad;
val Used when executing the SETVAL command. buf is used in the IPC_STAT / IPC_SET command. Represents the data structure of the semaphore used in the kernel. The pointer used by array when using the GETALL / SETALL command.
The following program returns the value of the semaphore. When using the GETVAL command, the last parameter in the call is ignored:
intget_sem_val (intsid, intsemnum)
{
return (semctl (sid, semnum, GETVAL, 0));
}
The following is a practical example:
# defineMAX_PRINTERS5
printer_usage ()
{
int x;
for (x = 0; x <MAX_PRINTERS; x ++)
printf ("Printer% d :% d ", x, get_sem_val (sid, x));
}
The following procedure can be used to initialize a new semaphore value:
void init_semaphore (int sid, int semnum, int initval)
{
union semunsemopts;
semopts.val = initval;
semctl (sid, semnum, SETVAL, semopts);
}
Note that the last parameter in the system call semctl is a copy of the union type, not a pointer to the union type.
[Directory]
----------------------------------------------- ---------------------------------
Shared memory
Shared memory is a memory area shared by several processes. This can be said to be the fastest form of IPC because it does not require any intermediate operations (eg, pipes, message queues, etc.). It simply maps the memory segment directly into the address space of the calling process. Such a memory segment can be created by a process, and then other processes can read and write this memory segment.
The shared memory segment of each system also maintains an internal data structure shmid_ds in the system kernel. This data structure is defined in linux / shm.h as follows:
/ * One shmid data structure for each shared memory segment in the system. * /
Struct shmid_ds {
struct ipc_perm shm_perm; / * operation perms * /
int shm_segsz ; / * size of segment (bytes) * /
time_t shm_atime; / * last attach time * /
time_t shm_dtime; / * last detach time * /
time_t shm_ctime; / * last change time * /
unsigned short shm_cpid; / * pid of creator * /
unsigned short shm_lpid; / * pid of last operator * /
short shm_nattch; / * no. of current attaches * /
/ * the following are private * /
unsigned short shm_npages; / * size of segment (pages) * /
unsigned long * shm_pages; / * array of ptrs to frames-> SHMMAX * /
struct vm_area_struct * attaches; / * descriptors for attaches * /
};