Linux C semaphore Programming

Source: Internet
Author: User
Tags define definition semaphore

Semaphores

When we use thread operations to write programs in a multi-user system, multi-process system, or a mixed system, we often find that we have a piece of critical code, here, we need to ensure that a process (or thread execution) needs to exclusively access a resource.
Semaphores have a complex programming interface. Fortunately, we can easily provide ourselves with a simplified interface that is efficient enough for most semaphore programming problems.
To prevent multiple programs from accessing a shared resource at the same time, we need to generate and use a tag to ensure that only one thread is executed at a time in the critical section. Thread-related methods, we can use mutex or semaphores to control a multi-threaded program's access to the critical section.

It is very difficult to write code for a general purpose to ensure a Program exclusive access to a specific resource, even though there is an algorithm solution called Dekker. Unfortunately, this algorithm depends on "Waiting" or "spin lock", that is, the continuous operation of a process needs to wait for a memory address to change. In a multitasking environment, such as Linux, this is a waste of CPU resources. If the hardware supports this, it is much easier, usually in the form of a specific CPU command to support exclusive access. The example supported by hardware is to add register values between access commands and atomic methods, so that no other commands can be run between read/Add/write operations.

One solution we have learned is to use the o_excl mark to call the OPEN function to create a file, which provides atomic file creation. This allows a process to successfully obtain a tag: the newly created file. This method can be used for simple problems, but it is cumbersome and inefficient for complicated situations.

When the semaphore concept was introduced by semi STR, the field of parallel programming took a big step forward. As we discussed in chapter 12th, semaphores are a special variable, an integer, and only two operations can increase the value: Wait (wait) and signal (signal ). Because in Linux and Unix programming, "wait" and "signal" have special meanings, we will use the original concepts:
P used for waiting (wait) (semaphore variable)
V used for signal (semaphore variable)

These two letters are from Dutch that waits (passeren: pass, like the detection point before the critical section) and signals (vrjgeven: specify or release, like releasing control of the critical section. Sometimes we also encounter the terms "up" and "down" related to semaphores, from the use of signal markers.

Semaphore Definition

The simplest semaphore is a variable with only 0 and 1 values. It is a binary semaphore. This is the most common form. Semaphores with multiple positive values are called Universal semaphores. In the rest of this chapter, we will discuss binary semaphores.

The definitions of P and V are surprisingly simple. Assume that we have a semaphore variable Sv. The two operations are defined as follows:

P (SV) reduces SV if Sv is greater than 0. If the SV value is 0, the execution of this process is suspended.
V (SV) resumes execution if a process is suspended and waits for Sv. If no Sv is pending, increase the Sv.

Another way to understand semaphores is that when the critical section is available, the semaphore variable Sv is true. When the critical section is busy, the semaphores variable is reduced by P (SV) to false, when the critical section is available again, it is increased by V (SV. Note: it is not enough to simply have a common variable that we can reduce or increase, because we cannot express the signal generation in C, C ++ or other programming languages, perform an atomic test to determine whether the variable is true. If yes, convert it to false. This is what makes semaphore operations special.

A theoretical example

We can use a simple theoretical example to learn how semaphores work. Suppose we have two processes, proc1 and proc2. These two processes will exclusive access to a database at a certain time they execute. We define a single binary semaphore, SV. Its initial value is 1 and can be accessed by two processes. The two processes then need to execute the same processing to access the critical code. In fact, these two processes can be different calls of the same program.

The two processes share the SV semaphore variable. Once a process has executed the P (SV) operation, the process can obtain the semaphore and enter the critical section. The second process is blocked for the critical section, because when he tries to execute P (SV), he will wait until the first process leaves the critical section and executes V (SV) to release the semaphore.

The process is as follows:

Semaphore SV = 1;
Loop forever {
P (SV );
Critical code section;
V (SV );
Noncritical code section;
}

This code is surprisingly simple, because P operations and V operations are very powerful. Figure 14-1 shows how P and V Operations become the threshold for code in the critical section.

Linux semaphores

Now we know what semaphores are and how they work in theory. Now we can take a look at how these features are implemented in Linux. The semaphores function interface is designed in a very fine manner and provides more practical performance than is usually needed. All Linux semaphore functions operate on a universal semaphore array instead of a single binary semaphore. At first glance, this seems to make things more complicated, but when a process needs to lock Multiple resources, operations on the semaphore array will be a great advantage. In this chapter, we will focus on using a single semaphore, because in most cases, this is exactly what we need to use.

The semaphore function is defined as follows:

# Include <sys/SEM. h>
Int semctl (INT sem_id, int sem_num, int command ,...);
Int semget (key_t key, int num_sems, int sem_flags );
Int semop (INT sem_id, struct sembuf * sem_ops, size_t num_sem_ops );

In fact, to obtain the # define definition required for a specific operation, we need to include sys/SEM. the H file usually needs to contain sys/types. H and sys/IPC. h file. In some cases, this is not necessary.

Because we will understand every function in sequence, remember that these functions are designed to operate the array of signal values, which will be more complicated than the operations required by a single semaphore.

Note that the key function is similar to a file name, because it indicates that the program may use or cooperate with resources. Similarly, the identifier returned by semget and used by other shared memory functions is very similar to the file * returned by the fopen function because it is used by the process to access shared files. Similar to files, different processes have different semaphore identifiers, even though they point to the same semaphore. Key and identifier usage are common for all IPC programs discussed here, although each program uses an independent key and identifier.

Semget

The semget function creates a new semaphore or obtains an existing semaphore key value.

Int semget (key_t key, int num_sems, int sem_flags );

The first parameter key is an integer that allows unrelated processes to access the same semaphore. All semaphores are indirectly accessed by providing a key for different programs. A semaphores identifier is generated for each semaphores system. The semaphore key value can only be obtained by semget. The semaphore identifier used by all other semaphore functions is returned by semget.

There is also a special semaphore key value, ipc_private (usually 0), which is used to create a semaphore that can only be accessed by the creation process. This is usually not useful, but fortunately, in some Linux systems, the manual page lists ipc_private as a bug because it does not prevent other processes from accessing semaphores.

The num_sems parameter is the number of required semaphores. This value is usually 1.

The sem_flags parameter is a tag set, which is similar to the open function tag. The Lower Nine bits are the signal permissions, which are similar to the file permissions. In addition, these tags can be used with ipc_creat or operated to create a new semaphore. Setting ipc_creat flag and specifying an existing semaphore key value is not an error. If not, the ipc_creat mark is ignored. We can use the combination of ipc_creat and ipc_excl to ensure that we can obtain a new and unique semaphore. If the semaphore already exists, an error is returned.

If successful, the semget function returns a positive number, which is the identifier used for other semaphore functions. If it fails,-1 is returned.

Semop

The semop function is used to change the semaphore value:

Int semop (INT sem_id, struct sembuf * sem_ops, size_t num_sem_ops );

The first parameter, sem_id, is the semaphore identifier returned by the semget function. The second parameter, sem_ops, is a pointer to a structure array. Each structure contains at least the following members:

Struct sembuf {
Short sem_num;
Short sem_op;
Short sem_flg;
}

The first member, sem_num, is the number of semaphores, usually 0, unless we are using a semaphores array. The sem_op member is the variable value of the semaphore. (We can change the signal value in any amount, not just 1) we usually use two values,-1 is our P operation, used to wait for a semaphore to become available, + 1 is our V operation, which is used to notify a semaphore that it is available.

The last member, sem_flg, is usually set to sem_undo. This will allow the operating system to track the changes made by the current process to the semaphore. if the process is terminated and the semaphore is not released, if the semaphore is occupied by this process, this flag allows the operating system to automatically release this semaphore. Setting sem_flg to sem_undo is a good habit Unless we need different actions. If we need a different value instead of sem_undo, consistency is very important. Otherwise, we will become very confused. When our process exits, does the kernel try to clean up our semaphores.

The action used by semop works at the same time to avoid competition conditions caused by the use of multiple semaphores. We can learn more about semop processing on the album page.

Semctl

The semctl function allows direct control of semaphore information:

Int semctl (INT sem_id, int sem_num, int command ,...);

The first parameter, sem_id, is the semaphore identifier obtained by semget. The sem_num parameter indicates the number of semaphores. This parameter is used when the semaphore array is used. Generally, if this is the first and only semaphore, the value is 0. The command parameter is the action to be executed. If an additional parameter is provided, it is unionsemun. According to the X/open specification, this parameter includes at least the following parameters:

Union semun {
Int val;
Struct semid_ds * Buf;
Unsigned short * array;
}

Many versions of Linux define semun union in header files (typically Sem. h), although x/open validation says we must define our own Union. If we find that we really need to define our own Union, we can refer to the semctl manual page to learn about the definition. In this case, we recommend that you use the definition provided on the manual page, although this definition is different from the previous one.

Multiple command values can be used for semctl. Here we describe two frequently used values. For more information about semctl, see the manual page.

The two common command values are:

Setval: used to initialize the semaphore to a known value. The required value is passed as the Val Member of the Union semun. You must set a semaphore before using it for the first time.
Ipc_rmid: Used to delete a semaphore ID when the semaphore is no longer needed.

The semctl function returns different values based on the command parameter. For setval and ipc_rmid, if successful, 0 is returned; otherwise,-1 is returned.

Use semaphores

As we can see in the previous section, the semaphore operation is quite complex. This is unfortunate because it is very difficult to use a critical section for multi-process or multi-thread programming, and its own complex programming interfaces also increase the programming burden.

Fortunately, we can use the simplest binary semaphores to solve most of the problems that require semaphores. In our example, we will use all the programming interfaces to create a very simple P for Binary semaphores.
And V type interface. Then, we will use this simple interface to demonstrate how semaphores work.

To test semaphores, we will use a simple program, sem1.c, which we can call multiple times. We will use an optional parameter to identify whether the program is responsible for creating or destroying semaphores.

We use the output of two different characters to identify the entry and exit of the critical section. A program called with parameters will output an X when it enters and leaves its critical section, and another program call will output an O when it enters and leaves its critical section. Because only one process can enter its critical section at any specified time, all X and O characters are paired.

Test-semaphores

1. After the # include statement, we define the function prototype and global variables, and then enter the main function. Here, we use the semget function to call to create a semaphore. This will return a semaphore ID. If the program is called for the first time (for example, it uses a parameter and argc> 1), the program will call set_semvalue to initialize the semaphore and set op_char to X.

# Include <stdio. h>
# Include <stdlib. h>
# Include <unistd. h>

# Include <sys/types. h>
# Include <sys/IPC. h>
# Include <sys/SEM. h>

# Include "semun. H"

Static int set_semvalue (void );
Static void del_semvalue (void );
Static int semaphore_p (void );
Static int semaphore_v (void );

Static int sem_id;

Int main (INT argc, char ** argv)
{
Int I;
Int pause_time;
Char op_char = 'O ';

Srand (unsigned INT) getpid ());

Sem_id = semget (key_t) 1234, 1, 0666 | ipc_creat );

If (argc> 1)
{
If (! Set_semvalue ())
{
Fprintf (stderr, "failed to initialize semaphore/N ");
Exit (exit_failure );
}
Op_char = 'X ';
Sleep (2 );
}
2. Then we use a circular code to enter and exit the critical section 10 times. The semaphore_p function is called. This function sets the semaphore and waits for the program to enter the critical section.
For (I = 0; I <10; I ++)
{
If (! Semaphore_p () Exit (exit_failure );
Printf ("% C", op_char); fflush (stdout );
Pause_time = rand () % 3;
Sleep (pause_time );
Printf ("% C", op_char); fflush (stdout );
3. After the critical section, we call the semaphore_v function. After a random wait, we enter the for loop again and set the semaphore to available. After the loop, call del_semvalue to clear the code.
If (! Semaphore_v () Exit (exit_failure );

Pause_time = rand () % 2;
Sleep (pause_time );
}

Printf ("/n % d-finished/N", getpid ());

If (argc> 1)
{
Sleep (10 );
Del_semvalue ();
}

Exit (exit_success );
}

4. The set_semvalue function uses the setval command in a semctl call to initialize the semaphore. We need to do this before using semaphores.

Static int set_semvalue (void)
{
Union semun sem_union;

Sem_union.val = 1;
If (semctl (sem_id, 0, setval, sem_union) =-1) return 0;
Return 1;
}
5 The del_semvalue function has almost the same format. The difference is that semctl uses the ipc_rmid command to remove the semaphore ID:

Static void del_semvalue (void)
{
Union semun sem_union;

If (semctl (sem_id, 0, ipc_rmid, sem_union) =-1)
Fprintf (stderr, "failed to delete semaphore/N ");
}

6 The semaphore_p function reduces the semaphore by 1 (wait ):

Static int semaphore_p (void)
{
Struct sembuf sem_ B;

Sem_ B .sem_num = 0;
Sem_ B .sem_op =-1;
Sem_ B .sem_flag = sem_undo;
If (semop (sem_id, & sem_ B, 1) =-1)
{
Fprintf (stderr, "semaphore_p failed/N ");
Return 0;
}
Return 1;
}

7. The semaphore_v function sets the sem_op part of the sembuf structure to 1 to make the semaphore available.

Static int semaphore_v (void)
{
Struct sembuf sem_ B;

Sem_ B .sem_num = 0;
Sem_ B .sem_op = 1;
Sem_ B .sem_flag = sem_undo;
If (semop (sem_id, & sem_ B, 1) =-1)
{
Fprintf (stderr, "semaphore_v failed/N ");
Return 0;
}
Return 1;
}

Note: This simple program only has one binary semaphore for each program. Although if we need multiple semaphores, we can extend this program to pass multiple semaphores. Generally, a simple binary semaphore is enough.

We can call this program multiple times to test our program. For the first time, we pass a parameter to notify the program that it is not responsible for creating and deleting semaphores. Parameters are not passed in another call.

The following is the sample output of two calls:

$./Sem1 1 &
[1] 1082
$./Sem1
Ooxxooxxooxxooxxooxxooxxooxxooxxooxxooxxxx
1083-finished
1082-finished
$

As we can see, O and X appear in pairs, indicating that the critical section is correctly processed. If this program cannot run normally on our system, we may need to use the stty-tostop command before calling the program to ensure that the background program that generates tty output will not generate signals.

Working Principle

This program starts from the key obtained by using the semget function to generate a semaphore sign. The ipc_creat flag allows you to create a semaphore if needed.

If this program has parameters, he is responsible for using our set_semvalue function to initialize the semaphore, which is a simplified interface of the more general semctl function. It also uses the provided parameters to determine which character to output. Sleep makes it easy for us to call another copy of the program multiple times before the program is executed. In the program, we use srand and Rand to introduce some pseudo-random counts.

This program loops for ten times and waits for a random period of time in its critical section and non-critical section. The code in the critical section is protected by calling our semaphore_p and semaphore_v functions. These two functions are a simplified interface for more general semop functions.

Before the semaphore is deleted, program copies called by parameters will wait until other calls are completed. If the semaphore is not deleted, it will continue to exist in the system, even though no program has used it. In actual procedures, it is very important to ensure that we do not have any legacy signals. When we run the program for the next time, the remaining semaphores will cause problems, and the semaphores are resource restrictions. We must use them with caution.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.