Interprocess communication

Posted by malam on Fri, 01 Oct 2021 21:05:34 +0200

Shared memory:
Normally, after the two processes are born by their father, even if they are brothers, their two memories are independent.
If the agreement is made in advance: there is a space in memory where brothers can get data in the future. (Critical Zone)
In addition to the fact that the parent process can create shared memory for the child process, two completely unrelated processes can also create shared memory through some kind of connection.
The process of kinship and the process of non kinship
Kinship: father, child, brother

  1. shm share_memory
    Kinship: father + child / brother
    shmget allocates a system memory shared segment
    #Include < sys / IPC. H > / / IPC, interprocess communication
    #include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg); // size, shmflg type
    The returned value is related to the key. The key is the same and the returned id is the same
    The id of the shared memory segment is returned
    The size of a shared memory segment is equal to rounding up to n * page_size (usually 4k)
    When the value in key is IPC_ IPC is set in private or shmflg_ Private then a shared memory will be created, otherwise no shared memory will be created.
    If IPC is set in shmflg_ CREAT | IPC_ Excl. however, the collection shared memory segment already exists, and the creation will fail.
    shmflg:
    IPC_CREATE: create a segment. If this flag is not used, shmget will find a segment related to the key value, and then check whether the user has permission to use this segment.
    IPC_EXCL: accompanying IPC_ Use creat to ensure that this call is used to create a segment. If the segment already exists, the call fails.
    Get address from id: shmat
    This function returns the id of a region. We can't access the memory directly, so we need to convert the id into an address.
  2. shmat: find the address by id
    void *shmat(int shmid, const void *shmaddr´╝îint shmflg);
    int shmdt(const void *shmaddr);
    shmat obtains the address space of the shared memory segment through shmid
    The address obtained by the same key may be different, because this address is the address of its own space, and the address attached to it is specified by shmaddr.
    Concentration of shmaddr in shmat
    1. If shamaddr is empty, you will automatically select an appropriate page to store segments.
    2. If shmaddr is not empty, but SHM is set in shmflg_ Rnd, then the address stored on shmaddr is rounded down to be close to SHMLBA
    3. Otherwise, shmaddr must be an integer multiple of the page size.
      A successful shmat call updates the member shimid_ds structure:
      1. shm_atime is set to the latest time
      2. shm_lpid is set to process id
      3. shm_nattch increased by 1
        Successful execution of the shmdt call will update:
      4. shm_Dtime is set to the most recent time
      5. shm_lpid is set to process id
      6. shm_nattch minus 1
        If shmat is executed successfully, the attachment address of the shared segment will be returned. If it fails, it will return - 1
        If shmdt is executed successfully, it returns 0. If it fails, it returns - 1
  3. ftok
    ket_t ftok(const *pathname, int proj_id); // Convert the pathname and a project id to a key
    The key returns an id, then uses shmget to get a segment id through this id, and then obtains the address through shmat.
//Code version 1
#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
};

struct Num *share_memory;

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory == NULL) {
        perror("shmat");
        exit(1);
    }
    for (int i = 1; i <= INS ;i++) {
        if ((pid == fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }
    return 0;
}

However, such code can be run for the first time. When running for the second time, the shared segment has been created and the file needs to be deleted to continue running.
View shared segments with ipcs.
ipcrm deleting shared segments

// In code version 2, we ignore the problem of shared segments first
#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
};

struct Num *share_memory;


void do_add() {
    while (1) {
        if (share_memory->now > 100) break;
        share_memory->now++;
        share_memory->sum += share_memory->now;
    }
    return ;
}

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory == NULL) {
        perror("shmat");
        exit(1);
    }
    share_memory->now = 0;
    share_memory->sum = 0;
    for (int i = 1; i <= INS ;i++) {
        if ((pid == fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }

    if (pid == 0) {
        do_add();
    } else {
        for (int i = 1; i <= INS; i++) {
            wait(NULL);
        }
    }
    printf("share_memory->sum = %d\n", share_memory->sum);
    return 0;
}

After running, you will find segment errors and access problems that you should not access
Where might it appear?
We only have in share_ When memory accesses the address...
So the parent process is share_ After the memory process assigns a value, it makes an output. It is found that there is no output and an error occurs. The location of the segment error is determined.
So it goes back to share_ After memory, perror outputs it, and the prompt permission deny is found
Therefore, it is noted that when declaring the id, the read-write permission is not given, so the flag is changed to IPC_CREAT | IPC_EXCL | 0666

#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
};

struct Num *share_memory;


void do_add(int x) {
    while (1) {
        if (share_memory->now > 1000) break;
        share_memory->sum += share_memory->now;
        share_memory->now++;
        printf("Child : %d, share_memory->now : %d, share_memory->sum : %d\n", \
               x, share_memory->now, share_memory->sum);
        usleep(100);
    }
    return ;
}

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory < 0) {
        perror("shmat");
        exit(1);
    }
    share_memory->now = 0;
    share_memory->sum = 0;
    for (int i = 1; i <= INS ;i++) {
        if ((pid = fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }

    if (pid == 0) {
        do_add(x);
    } else {
        for (int i = 1; i <= INS; i++) {
            wait(NULL);
        }
    }
    printf("share_memory->sum = %d, My Pid : %d\n", share_memory->sum, getpid());
    return 0;
}

The above code can be successfully executed. There is only one cpu on a one core machine, but there is still a possibility of error. Therefore, in order to avoid this possibility, we use the lock structure.

A core can only run one process at a time. Why is there a small possibility of error?
In do_ In add, the operation of auto increment of now value and sum accumulation is not atomic. One possibility is that now has just auto increment, but the time slice is up and the next process is run, which leads to the possibility of missing addition.

The essence of locking is to lock resources, locking resources, not processes

Mutex:
We can use thread mutexes.
Thread mutex
mutex: mutually exclusive
pthread_mutex_init: thread mutex initialization
pthread_mutex_unlock: thread mutex unlock
pthread_mutex_destroy: thread mutex destroyed
pthread_mutex_lock: lock of thread mutex
pthread_mutex_trylock: attempt lock of thread mutex

  1. pthread_mutex_init :
    int pthread_ mutex_ Init (pthread_mutex_t * mutex, const pthread_muteattr_t muteattr) lock pointer, lock attribute pointer

  2. pthread_mutexattr_
    pthread_mutexattr_setpshared : set process shared
    int pthread_mutexattr_setpshared(const pthread_mutexattr_t *arr, int pshared);
    pthread_mutexattr_init: mutex attribute initialization
    int pthread_mutexattr_init(pthread_mutexattr_t *attr);
    This lock is declared in the shared area so that all processes can see it.

// Code after locking
#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
    pthread_mutex_t mutex;
};

struct Num *share_memory;


void do_add(int x) {
    while (1) {
        pthread_mutex_lock(&share_memory->mutex);
        if (share_memory->now > 1000) {
            pthread_mutex_unlock(&share_memory->mutex);
            break;
        };
        share_memory->sum += share_memory->now;
        share_memory->now++;
        pthread_mutex_unlock(&share_memory->mutex);
        printf("Child : %d, share_memory->now : %d, share_memory->sum : %d\n", \
               x, share_memory->now, share_memory->sum);
        usleep(100);
    }
    return ;
}

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory < 0) {
        perror("shmat");
        exit(1);
    }
    share_memory->now = 0;
    share_memory->sum = 0;

    // Lock initialization
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, 1);

    pthread_mutex_init(&(share_memory->mutex), &attr);

    for (int i = 1; i <= INS ;i++) {
        if ((pid = fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }

    if (pid == 0) {
        do_add(x);
    } else {
        for (int i = 1; i <= INS; i++) {
            wait(NULL);
        }
    }
    shmdt(share_memory);
    perror("shmdt");
    printf("share_memory->sum = %d, My Pid : %d\n", share_memory->sum, getpid());
    return 0;
}

To use a lock, you need to initialize a lock attribute, and then place the lock where needed to initialize the lock

Lock condition variable pthread_cond_…
Lock condition variable is a synchronous device that allows threads to suspend or abandon the CPU when they encounter a condition
The basic operations of condition variables are: 1. Send a signal, 2. Wait for the signal, and the suspended thread will execute after receiving the signal

Analogy: when a friend asks me to eat, I'm not hungry. I eat when I'm hungry. The thread of my friend will be suspended, and then when I'm hungry, I send a signal, hungry!, The friend receives the signal and runs

The condition variable must be based on the lock. This is to prevent the signal from being sent before the thread waiting for the signal. (avoid that my friend began to wait for the signal when I was hungry, so he missed the signal)

pthread_cond_init initializes a condition variable. Cond may be required to initialize this condition variable_ Attr (conditional variable attribute). If there is no such attribute, it is cond_ If attr is set to null, Linux threads support conditional variables without attributes, so conditional attributes are ignored

Pthread can be used_ COND_ Initialize static initialization condition variable

Pthread_cond_signal will only restart a waiting thread. If there is no thread waiting for this condition variable, nothing will happen. If there are many condition variables waiting for you, one will be restarted, but I'm not sure which one

Conditional variable broadcast (pthread_cond_broadcast) restarts all waiting threads. If there is no waiting, it is the same as above

The wait condition variable (pthread_cond_wait) will automatically unlock the mutex (pthread_unlock_mutex will be executed automatically), and then wait for the condition signal response. The thread will be suspended without taking any time until the signal response. Before waiting for the condition variable, the mutex must be locked, and the mutex will be obtained again after waking up
Therefore, the overall process is as follows: Lock first - > find the signal to wait: Unlock - > wait - > wake up - > lock

The above unlocking operation is performed automatically. Therefore, if all threads wait for this signal to occupy the lock, it is necessary to ensure that the condition variable does not send a signal when locking and waiting bar i press the variable, that is, atomicity

Pthread_cond_timedwait, as the name suggests, does not wait after the timeout

Conditional destruction (pthread_cond_destruction)

// Complete without BUG code
#include "head.h"
#define INS 5

struct Num {
    int now;
    int sum;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
};

struct Num *share_memory;


void do_add(int x) {
    while (1) {
        pthread_mutex_lock(&share_memory->mutex);
        // printf("Child : %d Begin wait!\n", x);
        pthread_cond_wait(&share_memory->cond, &share_memory->mutex);
        int flag = 0;
        for (int i = 0; i < 100; i++) {
            if (share_memory->now > 1000) {
                pthread_mutex_unlock(&share_memory->mutex);
                usleep(100); //Take a short break to prevent receiving signals before waiting
                pthread_cond_signal(&share_memory->cond);
             // printf("Child %d exit\n" , x);
                shmdt(share_memory); // Each child process should delete its attachment before exiting
                printf("Child %d exit\n", x);
                exit(0);
                break;
            };
            share_memory->sum += share_memory->now;
            share_memory->now++;
            printf("Child : %d, share_memory->now : %d, share_memory->sum : %d\n", \
                x, share_memory->now, share_memory->sum);
            fflush(stdout);
        }
        pthread_mutex_unlock(&share_memory->mutex);
        pthread_cond_signal(&share_memory->cond);
    }
    return ;
}

int main() {
    pid_t pid;
    int x = 0, shmid;
    key_t key = ftok(".", 2021);
    if ((shmid = shmget(key, sizeof(struct Num), IPC_CREAT | IPC_EXCL | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    share_memory = (struct Num*) shmat(shmid, NULL, 0);
    if (share_memory < 0) {
        perror("shmat");
        exit(1);
    }
    share_memory->now = 0;
    share_memory->sum = 0;

    // Lock initialization
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, 1);

    //Initialize environment variables
    pthread_condattr_t c_attr;
    pthread_condattr_init(&c_attr);
    pthread_condattr_setpshared(&c_attr, 1);
    pthread_cond_init(&share_memory->cond, &c_attr);

    pthread_mutex_init(&(share_memory->mutex), &attr);

    for (int i = 1; i <= INS ;i++) {
        if ((pid = fork()) < 0) {
            perror("fork");
        }
        x = i;
        if (pid == 0) break;
    }

    if (pid == 0) {
        do_add(x);
    } else {
        usleep(500);// If you don't rest, the child process may not wait when the signal is sent
        printf("Parent!\n");
        pthread_cond_signal(&share_memory->cond);
        for (int i = 1; i <= INS; i++) {
            wait(NULL);
        }
    }
    printf("share_memory->sum = %d, My Pid : %d\n", share_memory->sum, getpid());
    shmdt(share_memory);// Delete the attached part of the parent process
    shmctl(shmid, IPC_RMID, NULL); // Shared segments can be deleted only in shmctl.
    perror("shmctl");
    return 0;
}

shmctl can control the shared segment part
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
Control the shared segment pointed to by shmid through the part of cmd

struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          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 */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };

SHM is mentioned in shmat and shmdt_ atime, shm_ dtime, shm_ nattch

struct ipc_perm {
               key_t          __key;    /* Key supplied to shmget(2) */
               uid_t          uid;      /* Effective UID of owner */
               gid_t          gid;      /* Effective GID of owner */
               uid_t          cuid;     /* Effective UID of creator */
               gid_t          cgid;     /* Effective GID of creator */
               unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
           };

cmd :

  1. IPC_STAT: copy the information contained in shmid from the kernel to the third parameter of this function. When executing this command, the premise is that the process is readable for the shared segment
  2. IPC_SET: declare a shmid_ds information, and then execute this command to write shmid information to the kernel. At this time, SHM will be updated_ CTime, but not all contents can be changed. SHM can be changed_ perm.uid, shm_ Perm.gid, and SHM_ In the last 9 bits of perm.mode, only the user of the current process or a user with higher permissions (such as root) c an change these values
  3. IPC_RMID: destroy the marked segment. Before destroying this segment, ensure that the last attached process also needs to be de attached. The caller must be the process that created the shared segment or a more authoritative process. In this command, buf this parameter is ignored. The caller must ensure that this segment is finally destroyed, otherwise the memory page will remain in the memory exchange area all the time
  4. IPC_INFO: returns the width limit and parameters of the system shared memory. The data structure of this buf is shmimfo, so a type conversion is required when using it
struct shminfo {
                         unsigned long shmmax; /* Maximum segment size */
                         unsigned long shmmin; /* Minimum segment size;
                                                  always 1 */
                         unsigned long shmmni; /* Maximum number of segments */
                         unsigned long shmseg; /* Maximum number of segments
                                                  that a process can attach;
                                                  unused within kernel */
                         unsigned long shmall; /* Maximum number of pages of
                                                  shared memory, system-wide */
                     };
  1. shm_info: returns an shm_info structure, which contains the information that system resources are used by shared segments
struct shm_info {
                         int           used_ids; /* # of currently existing
                                                    segments */
                         unsigned long shm_tot;  /* Total number of shared
                                                    memory pages */
                         unsigned long shm_rss;  /* # of resident shared
                                                    memory pages */
                         unsigned long shm_swp;  /* # of swapped shared
                                                    memory pages */
                         unsigned long swap_attempts;
                                                 /* Unused since Linux 2.4 */
                         unsigned long swap_successes;
                                                 /* Unused since Linux 2.4 */
                     };

The following commands are unique to Linux
1.SHM_STAT: returns a shmid_ds data structure, comparison and IPC_ Unlike stat, shmid is not the id of the shared segment returned by shmget, but the index of the shared segment in the kernel

With the following command, the caller can prevent or allow the exchange of shared segments (swap out memory)

  1. SHM_LOCK: prevent the exchange of shared segments
  2. SHM_UNLOCK: Unlock the segment and allow it to exchange

Topics: Linux Operating System