LinuxC interprocess communication — semaphore

When multiple processes/threads perform shared operations, it is used for resource protection to prevent mutual interference

The semaphore is actually a shared variable created by the OS. The process will check the value of this variable before operating. The value of this variable is a mark. Through this mark, you can know whether it can be operated to achieve mutual exclusion.

Using steps of semaphore

1. The process calls the semget function to create a new semaphore set, or obtain an existing semaphore set.

2. Call the semctl function to set the initial value for each semaphore in the collection

3. Call the semop function to perform pv operation on the semaphore in the set (lock and unlock)

4. Call semctl to delete the semaphore set

Mutex demo:

#include <stdio.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/sem.h>
#include <signal.h>
#include <sys/ipc.h>

#define SEM_NAME "./sem_file"
int semi;

void init_sem(int semid, int sem_num, int val)
{
    // int semctl(int semid, int semnum, int cmd, ...);
    if (semctl(semid, sem_num, SETVAL, val) < 0)
    {
        perror("sem set val error");
        exit(1);
    }
    /**
     * Function: Control each semaphore in the set according to the requirements of cmd, ... means it is a variable parameter function,
     * If the fourth parameter is not used, it can be omitted.
     * Return value: the call returns a value other than -1 if the call succeeds, and -1 if it fails, and errno is set.
     * semnum: The number of a semaphore in the set. The number of the semaphore is a non-negative integer, and it is automatically numbered from 0.
     * You can find the corresponding semaphore in the set through the semaphore number, and then control the specific semaphore.
     * cmd: control options.
     * - IPC_STAT: Read the attribute information of the semaphore from the kernel to the struct semid_ds cache specified by the fourth parameter.
     * - IPC_SET: Modify attribute information. At this time, the specific modification method of the struct semid_ds structure variable is the same as that of the message queue and shared memory.
     * - IPC_RMID: Delete the semaphore. When all the semaphores in the set are deleted, the semaphore set is also deleted.
     * The fourth parameter is not used in the delete operation, so the fourth parameter is omitted.
     * For example: semctl(semid, 0, IPC_RMID);
     * - SETVAL: Through the fourth parameter, set an int initial value for the semnu numbered semaphore in the set.
     * As mentioned earlier, if it is a binary semaphore, the initial value is either 0 or 1. If the purpose of the semaphore is mutual exclusion, it is basically set to 1.
     * When set to 1, when several processes are mutually exclusive, the one who runs first will operate first.
     * If it is synchronous, whether the initial value is 1 or 0 depends on the specific situation.
     * The IPC_STAT, IPC_SET, and IPC_RMID of the semaphore are the same as the IPC_STAT, IPC_SET, and IPC_RMID of the message queue and shared memory.
     * But SETVAL is indeed an option unique to process semaphores.
     * For semaphores, IPC_RMID and SETVAL are the two most commonly used options.
     **/
}

int create_sem(int nsems) // create semaphore
{
    int semi;
    key_t key;
    key = ftok(SEM_NAME, 'p');

    semid = semget(key, nsems, 0655 | IPC_CREAT);
    // int semget(key_t key, int nsems, int semflg);
    /**
     * Function: Create a new or obtain an existing semaphore set according to the key value, and return its identifier.
     * When implementing mutual exclusion: only one semaphore is required in the set
     * When implementing synchronization: multiple semaphores are required in the set
     * parameter
     * Key: Set the same message queue and shared memory. Generally, ftok is used to obtain the key value.
     * nsems: specifies the number of semaphores in the set. When used for mutual exclusion, the quantity is specified as 1, because only one semaphore is required
     * If it is synchronization, it needs to be at most multiple. As for how many, we will talk about synchronization.
     * · semflg: Set the same message queue and shared memory. Generally set to 0664 | IPC_CREAT.
     * return value:
     * · If the call is successful, it returns the identifier of the semaphore set, and if it fails, it returns -1, and errno is set.
     */
    if (semi < 0)
    {
        perror("sem get error");
        exit(1);
    }

    return semi;
}

void delete_sem(int sem_num)
{ // ipcrm -s semid removes the signal identified by semid
    if (semctl(semid, sem_num, IPC_RMID) < 0) // ipcrm -S semkey removes the signal created with semkey
    {
        perror("delete sem error");
        exit(1);
    }
    printf("delete sem\
");
}

void my_exit(int sig)
{
    delete_sem(0);
    remove(SEM_NAME);
    exit(0);
}

int main(int argc, char const *argv[])
{
    pid_t pid;

    int fd = open("a.txt", O_WRONLY | O_CREAT, 0655);
    if (fd == -1)
    {
        perror("open file error");
        exit(1);
    }
    // only create a semaphore
    semid = create_sem(1); // Create a signal set semid -- the symbol of the semaphore

    init_sem(semid, 0, 1); // Initialize the value of the specified semaphore

    pid = fork();

    if (pid < 0)
    {
        perror("fork error");
        exit(1);
    }
    if (pid == 0)
    {
        signal(SIGINT, my_exit);

        struct sembuf my_sem_buf[1];
        my_sem_buf[0].sem_num = 0;
        my_sem_buf[0].sem_op = -1; // lock -1
        my_sem_buf[0].sem_flg = SEM_UNDO; // prevent deadlock

        while (1)
        {
            // lock
            my_sem_buf[0].sem_op = -1; // lock -1
            semop(semid, my_sem_buf, 1);
            // int semop(int semid, struct sembuf *sops, unsigned nsops);
            /**
             * - structure member
             * struct sembuf
             * {
             * unsigned short sem_num;
             * short sem_op;
             * short sem_flg;
             * }
             * This structure does not need to be defined by us, because it is already defined in the header file of semop.
             * If you cannot judge whether this structure needs to be defined by us, then you should not define it
             * If the compilation prompts that the structure type does not exist, it means that it needs to be defined by itself. If the compilation passes, it means that it has been defined in the system header file.
             * + sem_num: semaphore number, which decides which semaphore in the set to perform pv operation
             * + sem_op: set to -1, means that you want -1 to perform p operation, set 1 to indicate that you want + 1 to perform v operation
             *+ sem_flg:
             * IPC_NOWAIT:
             * In general, if the p operation is performed when the value of the semaphore is 0, the p operation of semop will be blocked.
             * If you don't want to block, you can specify this option, NOWAIT means no blocking.
             * But unless in some special cases, we don't need to set it to non-blocking.
             * SEM_UNDO: prevent deadlock
             * Still take the binary semaphore as an example, when the process ends before the v operation, the value of the semaphore will always remain 0,
             * Then other processes will never succeed in the p operation, which will cause the process to sleep forever, which causes a deadlock.
             * But after setting the SEM_UNDO option, if there is no V operation at the end of the process, the OS will automatically help V operation to prevent deadlock.
             **/
            write(fd, "hello", 5);
            write(fd, "world", 5);
            write(fd, "\
", 1);

            my_sem_buf[0].sem_op = + 1; // unlock + 1
            semop(semid, my_sem_buf, 1);
            // unlook
        }
    }
    if (pid > 0)
    {
        struct sembuf my_sem_buf[1];
        my_sem_buf[0].sem_num = 0;
        my_sem_buf[0].sem_op = -1; // lock -1
        my_sem_buf[0].sem_flg = SEM_UNDO; // prevent deadlock

        while (1)
        {
            // lock
            my_sem_buf[0].sem_op = -1; // lock -1
            semop(semid, my_sem_buf, 1);

            write(fd, "wwww", 5);
            write(fd, "hhhhh", 5);
            write(fd, "\
", 1);

            my_sem_buf[0].sem_op = + 1; // unlock + 1
            semop(semid, my_sem_buf, 1);
            // unlock
        }
    }
    return 0;
}

———————————————– ————————————————– ———————————–

Semaphore realizes process synchronization:

How many processes need to be synchronized, we need to create a corresponding number of semaphores in the set

There are four processes 1, 2, 3, 4. The function of thread 1 is to output 1, the function of thread 2 is to output 2, and so on… Now there are four files ABCD. Initially all are empty. Now let the four files have the following format:
A: A B C D A B…
B: B C D A B C…
C: C D A B C D…
D: D A B C D A…
Please design the program. . code show as below:

shm.h:

#include <stdio.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/sem.h>
#include <signal.h>
#include <sys/ipc.h>

#define SEM_NAME "./sem_file"

void init_sem(int semid, int sem_num, int val);
int create_sem(int nsems);
void delete_sem(int semid, int sem_num);
void p_sem(int semid, int sem_num);
void v_sem(int semid, int sem_num);

shm.c

#include "sem.h"

void init_sem(int semid, int sem_num, int val)
{
    if (semctl(semid, sem_num, SETVAL, val) < 0)
    {
        perror("sem set val error");
        exit(1);
    }
}

int create_sem(int nsems)
{
    int semi;
    key_t key;
    key = ftok(SEM_NAME, 'p');

    semid = semget(key, nsems, 0655 | IPC_CREAT);

    if (semi < 0)
    {
        perror("sem get error");
        exit(1);
    }

    return semi;
}

void delete_sem(int semid, int sem_num)
{
    if (semctl(semid, sem_num, IPC_RMID) < 0)
    {
        perror("delete sem error");
        exit(1);
    }
    printf("delete sem\
");
}

void p_sem(int semid, int sem_num)
{
    struct sembuf my_sembuf[1];
    my_sembuf[0].sem_num = sem_num;
    my_sembuf[0].sem_op = -1;
    my_sembuf[0].sem_flg = SEM_UNDO;
    
    if(semop(semid,my_sembuf,1) < 0)
    {
        perror("sem op error");
        exit(1);
    }
}

void v_sem(int semid, int sem_num)
{
    struct sembuf my_sembuf[1];
    my_sembuf[0].sem_num = sem_num;
    my_sembuf[0].sem_op = + 1;
    my_sembuf[0].sem_flg = SEM_UNDO;
    
    if(semop(semid,my_sembuf,1) < 0)
    {
        perror("sem op error");
        exit(1);
    }
}

main.c

#include "sem.h"

int semi;
int fd[4];

void my_exit(int sig)
{
    delete_sem(semid, 0);
    remove(SEM_NAME);
    exit(0);
}

void tast(int semid, int num, int flag)
{
    char temp = 'A';
    temp = temp + num;
    p_sem(semid, num);
    for (int i = 0; i < 4; i ++ )
    {
        if (flag != 0 || i <= num)
        {
            write(fd[i], & temp, 1);
        }
    }
    v_sem(semid, (num + 1) % 4);
}

void open_file(int *fd)
{
    char temp[1];

    for (int i = 0; i < 4; i ++ )
    {
        temp[0] = 'A';
        temp[0] = temp[0] + i;
        fd[i] = open(temp, O_RDWR | O_CREAT | O_APPEND | O_TRUNC, 0655);
        if (fd[i] < 0)
        {
            perror("open file error");
            exit(1);
        }
    }
}

int main(int argc, char const *argv[])
{
    pid_t pid;

    semid = create_sem(4); // need four semaphores in the set

    for (int i = 0; i < 4; i ++ )
    {
        if (i == 0)
        {
            init_sem(semid, i, 1); // release A
        }
        else
        {
            init_sem(semid, i, 0); // lock B C D
        }
    }

    open_file(fd);

    if ((pid = fork()) < 0) // create process
    {
        perror("fork error");
        exit(1);
    }

    if (pid == 0)
    {
        pid_t pid2;

        if ((pid2 = fork()) < 0)
        {
            perror("fork 2 error");
            exit(1);
        }

        if (pid2 == 0) // The child process of the child process prints A in a loop and unlocks B
        {
            signal(SIGINT, my_exit);
            int flag = 0;
            while (1)
            {
                tast(semid, 0, flag);
                flag = 1;
            }
        }

        else if (pid2 > 0) // child process -- print B unlock C
        {
            int flag = 0;
            while (1)
            {
                tast(semid, 1, flag);
                flag = 1;
            }
        }
    }

    else if (pid > 0)
    {
        pid_t pid3;

        if ((pid3 = fork()) < 0)
        {
            perror("fork 2 error");
            exit(1);
        }

        if (pid3 == 0) // The child process of the parent process prints C and unlocks D
        {
            int flag = 0;
            while (1)
            {
                tast(semid, 2, flag);
                flag = 1;
            }
        }

        else if (pid3 > 0) // parent process print D unlock A
        {
            int flag = 0;
            while (1)
            {
                tast(semid, 3, flag);
                flag = 1;
            }
        }
    }

    return 0;
}