Things between linux processes

Keywords: Linux Programming Windows less

Before I wrote this article, I was afraid of the interprocess communication of linux. However, after reading some other articles, I found that the interprocess communication of linux is much more difficult than what I learned. First of all, the concept of thread under linux has been weakened. Thread is also called lightweight process. Thread mechanism is an abstraction commonly used in modern programming technology, which provides a group of threads sharing memory address control in the same program. These threads can share open files and other resources. Thread mechanism supports concurrent program technology, which can ensure real parallel processing. The mechanism of linux thread implementation is very unique. From the perspective of kernel, there is no concept of thread, and all threads are implemented as processes. The kernel does not prepare special data structures to represent threads. On the contrary, a thread is only regarded as a process that shares some resources with other processes. Each thread has its own task struct with unique rate. Therefore, in the kernel, it looks like a common process (only this level can share some resources with other processes, such as address space)

In windows or sun solaris and other operating systems, special mechanisms are provided to support threads. Threads are abstracted into a unit that consumes less resources and executes quickly. For linux, it is just a way to share resources among processes. The creation of linux thread is similar to that of ordinary process, except that when calling clone, some parameters need to be passed to indicate the shared resources

clone(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND,0);

And a common fork implementation

clone(SIGCHLD,0)

The implementation of vfork is

clone(CLONE_VFORK|CLONE_VM|SIGCHLD,0);

The parameter flag passed to clone() determines the behavior of the newly created process and the type of resources shared between the parent and child processes

As for exec(), it is used to specify the program to replace all the contents of the current process, so exec() series functions are often invoked after the first three functions are used to create a completely new program running environment. The call of fork is returned twice at a time, respectively in the parent process and the child process. The return value of the parent process is the process descriptor of the Forbidden City, and the return value of the child process is 0. When fork creates a process, the child process copies the data segment, stack and code segment of the parent process. In order to reduce the cost, linux adopts the write time replication technology, only when writing The memory to be written is usually a page of virtual memory.

vfork ensures that the child process runs first, that is, the process does not run before calling exec and exit, that is, if the child process depends on the further actions of the parent process, it will fall into deadlock. Before calling exec and exit again, the common code segment and data segment of child process and parent process.

Interprocess communication

Pipes and named pipes. Pipes can be used for inter process communication with kinship. Named pipes overcome the limitation of anonymity of pipes and allow inter process communication without kinship.

int main()
{
    int fd[2];
    pid_t pid;
    char line[1024];
    int nRead = 0;
    if(pipe(fd) != 0)
    {
        exit(0);
    }
    if(pid = fork() < 0)
    {
        exit(1);
    }
    else if(pid > 0)
    {
        close(fd[0]);
        write(fd[1],"\nhelloworld\n",14);
    }
    else
    {
        close(fd[1]);
        nRead = read(fd[0],line,1024);
        write(STDOUT_FILENO,line,nRead);
    }
    return 0;
}

Signal is used to inform the receiving process of an event. In addition to interprocess communication, the process can also send a signal to the process itself

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

static int alarm_fired = 0;

void sig_test(int sig)
{
    alarm_fired = 1;
}

int main()
{
    pid_t pid;
    pid = fork();
    switch(pid)
    {
    case -1:
        exit(1);
    break;
    case 0:
        sleep(5);
        kill(getppid(),SIGALRM);
        exit(0);
    break;
    }
    signal(SIGALRM,sig_test);    
    while(!alarm_fired)
    {
        printf("wait signal\n");
        sleep(1);
    }
    if(alarm_fired)
    {
        printf("\nI got a signal %d\n",SIGALRM);    
    }
    exit(0);
}

The chain list of messages in message queue, including posix message queue and system V message queue, has enough processes to add messages to the queue, and processes with read permission can read the pipeline messages in the queue

 

typedef struct {
    long type;
    char name[64];
    int height;
}Msg;
///process 1
int main()
{
    key_t key = ftok("/home/dou",'6');
    printf("key:%x\n",key);
    int msgid = msgget(key,IPC_CREAT|O_WRONLY|0777);
    if(msgid < 0)
        exit(-1);
    Msg msg;
    puts("please input your type name height:");
    scanf("%ld%s%d",&msg.type,msg.name,&msg.height);
    msgsnd(msgid,&msg,sizeof(msg)-sizeof(msg.type),0);
    return 0;
}

//process 2
int main()
{
    key_t key = ftok("/home/dou",'6');
    printf("key:%x\n",key);
    int msgid = msgget(key,O_RDONLY);
    if(msgid < 0)
        exit(-1);
    Msg rcv;
    long type;
    puts("please input your type you want:");
    scanf("%ld",&type);
    msgrcv(msgid,&rcv,sizeof(rcv)-sizeof(type),type,0);
    msgctl(msgid,IPC_RMID,NULL);
    return 0;
}

Shared memory, which enables multiple processes to access the same block of memory space, is the fastest available form of IPC. It is designed for the low efficiency of other communication mechanisms. It is often used in combination with other communication mechanisms, such as semaphores, to achieve synchronization and mutual exclusion between processes.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>


#define MAX_BUFFER_LEN 2048
struct shared_use_st
{  
    int written;//As a flag, non-0: indicates readable, 0 indicates writable 
    char text[MAX_BUFFER_LEN ];//Record written and read text
};

//process read
int main()
{
    int running = 1;
    void *shm = nullptr;
    struct shared_use_st *shared;
    int shmid;
    shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
    if(shmid == -1)
    {
        exit(0);
    }
    printf("\nMemory attached at %x\n",(int)shm);
    shared = (struct shared_use_st *)shm;
    shared->written = 0;
    while(running)
    {
        if(shared->written != 0)
        {
            printf("You wrote:%s",shared->text);
            sleep(rand()%3);
            shared->written = 0;
            if(strncmp(shared->text,"end",3) == 0)
            {
                running = 0;
            }
            else
            {
                sleep(1);
            }
        }
    }
    if(shmdt(shm) == -1)
    {
        exit(-1);
    }
    if(shmctl(shmid,IPC_RMID,0) == -1)
    {
        exit(-2);    
    }
    return 0;
}

//process write
int main()
{
    int running = 1;
    void *shm = nullptr;
    struct shared_use_st *shared = nullptr;
    char buffer[1024];
    int shmid;
    shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
    if(shmid == -1)
        return -1;
    shm = shmat(shmid,(void *)0,0);
    if(shm == (void *)-1)
    {
        return -1;    
    }
    printf("Memory attached at %x\n",(int)shm);
    shared = (struct shared_use_st*)shm;
    while(running)
    {
        while(shared->written == 1)
        {
            sleep(1);
            printf("Waiting...\n");
            
        }
        printf("Enter some text:");
        fgets(buffer,1024,stdin);
        strncpy(shared->text,buffer,1024);
        shared->written = 1;
        if(strncmp(buffer,"end",3) == 0)
            running = 0;
    }
    if(shmdt(shm) == -1)
    {
        fprintf(stderr,"shmdt failed\n");
        return -1;
    }
    return 0;
}

Semaphores are mainly used as synchronization means between processes and between different threads of the same process

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define PATHNAME "."
#define SEM_TEST_ID   0x5555

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *_buf;
};

int createsemset(int nums,int flags)
{
    key_t key = ftok(PATHNAME,SEM_TEST_ID);
    if(key < 0)
        return -1;
    int semid = semget(key,nums,flags);
    if(semid < 0)
        return -1;
    retur semid;
}

int initsem(int semid,int nums,int initval)
{
    union semun _un;
    _un.val = initval;
    if(semctl(semid,nums,SETVAL,_un) < 0)
        return -1;
    return 0;
}

int getsemset(int nums)
{
    return createsemset(nums,IPC_CREAT);
}

int commPV(int semid,int who,int op)
{
    struct sembuf _buf;
    _buf.sem_num = who;
    _buf.sem_op = op;
    _buf.sem_flg = 0;
    if(semop(semid,&buf,1)<0)
        return -1;
    return 0;
}
int destorysemset(int semid)
{
    if(semctl(semid,0,IPC_RMID)<0)
        return -1;
    return 0;
}

int main()
{
    int semid = createsemset(1,IPC_CREAT|IPC_EXCL|0666);
    initsem(semid,0,0);
    pid_t pid = fork();
    if(pid == 0)
    {
        int semid = getsemset(0);
        while(1){
            printf("A");
            fflush(stdout);
            usleep(101000);
            printf("A ");
            fflush(stdout);
            usleep(100000);
        }
    }
    else
    {
        while(1){
            printf("B");
            fflush(stdout);
            usleep(100100);
            printf("B ");
            fflush(stdout);
            usleep(120000);
        }
        wait(nullptr);
    }
    destorysemset(semid);
}

Finally, the most classic socket communication.

Let's recall something about threads

#include <pthread.h>

pthread_t pid;

void *func(void *arg);

Pthread [create (PID, nullptr, func, nullptr); / / create a thread

pthread_exit(0); / / end of run

pthread_join(pid); / / block and wait for the sub thread, to prevent the main thread from ending and the sub thread from running. At the same time, obtain the exit status of the sub thread

pthread_detach(); / / the thread is detached. After running, the resource will be recalled immediately

Sub thread call

pthread_detach(pthread_self()); / / non blocking immediate return. The sub threads are divided into separate state and join state. When the sub thread in join state finishes running, the resources will not be released. After calling pthread Ou join, the resources will be released. After the sub thread in separate state finishes running, the resources will be recycled automatically.

All threads can share memory and variables directly. The thread stack is allocated separately, but it is also in the same address space. Compared with multi processes, threads need to be synchronized and locked.

Basic synchronization mechanism provided by linux

Mutex mutex is essentially a lock. Lock the mutex before accessing the shared resources, and release the lock after accessing. After the mutex is locked, any other thread that attempts to lock the mutex again will be blocked until the current thread releases the mutex. If multiple threads block when the mutex is released, all blocked threads on the mutex will be programmed to run. The first thread that changes to the running state can lock the mutex and wait for others.

int pthread_mutex_init(pthread_mutex_t *m,pthread_mutexattr_t *attr);

int pthread_mutex_destory(pthread_mutex_t *);

int pthread_mutex_lock(pthread_mutex_t *m);

int pthread_mutex_unlock(pthread_mutex_t *m);

Spin lock: when the critical area is a small piece of code, the thread enters the critical area and exits immediately. At this time, the use of mutex will lead to a large number of context switching, so the spin lock appears. When waiting for the spin lock, the process will not release the cpu, but will always occupy the cpu, so the use of the spin lock is that the code in the critical area will not take too long, and the competition is not too fierce. Otherwise, a large number of threads waiting for spin lock are like deadlock.

#include <spinlock.h>

void spin_lock_init(spinlock_t *lock);

void spin_lock(spinlock_t *lock);

void spin_unlock(spinlock_t *lock);

Read write lock

Read and write locks are similar to mutexes, but allow for higher parallelism. Mutexes are either locked or unlocked, and only one thread can lock them at a time.

The read-write lock can be divided into three states: lock state in read mode, lock state in write mode and no lock state. Only one thread can occupy the read-write lock in write mode at a time, but multiple threads can occupy the read-write lock in read mode at the same time. In the read-write lock write lock state, before unlocking, all threads trying to lock this lock will be blocked. When the read lock state is reached, all threads trying to lock it in read mode can get access. However, if the thread wants to lock this lock in write mode, it must block until all threads release the read lock.

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);

int pthread_rwlock_destory(pthread_rwlock_t *rwlock);

pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

Condition variables, when used with mutexes, allow threads to wait for specific conditions to occur.

If a thread is waiting for a condition to appear in the shared data, the code can repeatedly lock and unlock the mutually exclusive object to check any change in the value, and at the same time, it will quickly unlock the eating object so that other threads can make any necessary changes. Need a way to wake up a thread sleeping waiting for a specific condition

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *thread1(void *);
void *thread2(void *);

int i = 1;
int main()
{
    pthread_t a,b;
    pthread_create(&a,nullptr,thread1,nullptr);
    pthread_create(&b,nullptr,thread2,nullptr);
    pthread_join(b,nullptr);
    pthread_mutex_destory(&mutex);
    pthread_cond_destory(&cond);
    exit(0);
}

void *thread1(void *arg)
{
    for(i = 1;i<9;i++)
    {
        pthread_mutex_lock(&mutex);
        if(i%3 == 0)
            pthread_cond_signal(&cond);
        else
            printf("thread1:%d\n",i);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}

void *thread2(void *arg)
{
    while(i<9)
    {
        pthread_mutex_lock(&mutex);
        if(i%3 != 0)
            pthread_cond_wait(&cond,&mutex);
        printf("thread2:%d\n",i);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}

pthread_cond_signal is only responsible for waking up a thread blocking on the same condition variable. If there are multiple threads, the system will automatically wake up one of them according to the scheduling policy. On multiprocessors, the function may wake up multiple threads at the same time, at the same time, the function has nothing to do with lock operation.

Semaphores allow multiple processes to enter critical areas

Int SEM init (SEM * SEM, int pshared, unsigned int value); / / initializes a specified number of semaphores. Pshared indicates whether to share between processes or threads

Int sem_destroy (sem_t * SEM); / / semaphore destroy

int sem_wait(sem_t *sem); / / wait for the semaphore. If the semaphore value is greater than 0, subtract one and return immediately.

int sem_post(sem_t *sem); / / release the semaphore and add 1 to its value

 

I have written a lot, but it's not too hard. It's all basic things. I feel it's easy to understand. But it seems that there is still some lack to build high-rise buildings with him. I'll make it up later. Come on

 

Published 35 original articles, won praise 4, visited 50000+
Private letter follow

Posted by gterre on Mon, 09 Mar 2020 00:09:18 -0700