Process communication and synchronization (rpm)

concept

Competitive conditions

Multiple processes reading and writing some shared data, and the final result depends on the exact am running processes, called competitive conditions.

Busy wait mutex

Several achieve mutually exclusive options:

  • Maskable interrupt

1 In a single processor system, the easiest way is to have each process immediately after entering the critical zone immediately block all interrupts, including clock interrupts. CPU interrupt occurs only when the process will be switched, so that interrupt the CPU will not be switched to other processes after being shielded.
  • Lock variable

  • Strict rotation method

    while (TRUE) {
          while (turn != 0) 
          critical_region(); 
          turn = 1;
          noncritical_region();
      }
    
    
      while (TRUE) {
          while (turn != 1) 
          critical_region(); 
          turn = 0;
          noncritical_region();
      }

Busy waiting examine variables. Use lock busy waiting is called a spin lock.

  • Peterson Solution
#define FALSE 0 
  #define TRUE  1
  #define N       2                    /* number of processes */
    
  int turn;                           /* whose turn is it? */
  int interested[N];                   /* all values initially 0 (FALSE) */

  void enter_region(int process);    /* process is 0 or 1 */
  {
      int other;

      other = 1 - process;
      interested[process] = TRUE;
      turn = process;
      while (turn == process && interested[other] == TRUE); 
  }


  void leave_region(int process)        /* process: who is leaving */ 
  {
      interested[process] = FALSE;   /* indicate departure from critical region */ 
  }
  • TSL instruction

The TSL (test and lock) instruction to read a memory word lock register RX and then a non-zero value stored in the memory address. Read and write operations to ensure the word is inseparable from that of other processors are allowed to access the memory word until the end of the instruction. The CPU will execute instructions TSL bus memory lock to prevent access to the memory before the other CPU in the present instruction ends.

enter region:
      TSL REGISTER, LOCK         | copy lock to register and set lock to 1
      CMP REGISTER, #0         | was lock zero?
      JNE enter_region         | if it was not zero, lock was set, so loop
      RET                     | return to caller; critical region entered

  leave region:
      MOVE LOCK, #0             | store a 0 in lock
      RET                     | return to caller 

An alternative is XCHG TSL instruction, its atomic content exchanged two positions, such as register and memory word. code show as below:

 enter region:
      MOVE REGISTER, #1          | put a 1 in the register
      XCHG REGISTER, LOCK     | swap the contents of the register and lock variable
      CMP REGISTER, #0         | was lock zero?
      JNE enter_region         | if it was non zero, lock was set, so loop
      RET                     | return to caller; critical region entered

  leave region:
      MOVE LOCK, #0             | store a 0 in lock
      RET                     | return to caller

Busy waiting problem of priority inversion. Assumed that there are two processes H and L, L lower priority scheduling rules as long as the H in the ready state can be allowed. At some point, L in the critical region, in which case H is changed to the ready state, and then begins busy waiting. However, due to H L it will not be scheduled when ready, will not be able to leave the critical region, so H will always be busy wait.

signal

Semaphore using integer variable to the cumulative number of wake. Semaphore value can be zero or a positive value. Semaphore has up and down both.

Use semaphores solve the producer - consumer issues:

#define N 100                     /* number of slots in the buffer */
typedef int semaphore;          /* semaphores are a special kind of int */
semaphore mutex = 1;              /* controls access to critical region */
semaphore empty = N;              /* counts empty buffer slots */
semaphore full = 0;             /* counts full buffer slots */

void producer(void) {
  int item;
  while (TRUE) {                 /* TRUE is the constant 1 */
    item = produce_item();      /* generate something to put in buffer */
    down(&empty);                  /* decrement empty count */
    down(&mutex);                /* enter critical region */
    insert_item(item);          /* put new item in buffer */
    up(&mutex);                 /* leavecriticalregion */
    up(&full);                     /* increment count of full slots */
  } 
}

void consumer(void) {
  int item;
  while (TRUE) {                   /* infinite loop */
    down(&full);                 /* decrement full count */
    down(&mutex);                 /* enter critical region */
    item = remove_item();         /* take item from buffer */
    up(&mutex);                  /* leavecriticalregion */
    up(&empty);                 /* increment count of empty slots */
    consume item(item);            /* do something with the item */
  } 
}

Interrupt semaphores can be implemented using the most natural approach is to provide each / O device sets a semaphore I, initial value 0. After the start I / O devices, on the implementation of a management process down operations immediately to the relevant semaphore, so the process is blocked. When the interrupt arrives, the interrupt handler then performs a correlation operation on the semaphore up, so that the process becomes ready state.

Semaphores can be used to implement mutual exclusion, it can also be used for event synchronization.

Mutex

It is a simplified version of the mutex semaphore, mutex called. Commonly used in the user thread packages. Mutex only two states: locked and unlocked.

XCHG TSL or instructions may conveniently implemented in user space mutex:

mutex_lock:
    TSL REGISTER, MUTEX                 | copy mutex to register and set mutex to 1
    CMP REGISTER, #0                     | was mutex zero?
    JZE ok                                 | if it was zero, mutex was unlocked, so return
    CALL thread_yield                    | mutex is busy; schedule another thread
    JMP mutex_lock                        | try again    
ok: RET                                    | return to caller; critical region entered
                                        

mutex_unlock:
    MOVE MUTEX, #0                         | store a 0 in mutex
     RET                                 | return to caller

mutex_lock and enter_region very similar, but there is a crucial difference. When enter_region enter the critical region fails, always in the process of repeating the test lock (busy wait) until the clock expires, reschedule other processes running.

In the user thread, because there is no clock to stop the run too long thread, it is necessary to make a call thread_yield CPU.

Pthread offers a variety of synchronization mechanisms, including the mutex and condition variables. Condition variable is always used in conjunction with a mutex. In addition there will be no memory condition variables, it is necessary to avoid loss of signal.

#include <stdio.h>
#include <pthread.h>
#define MAX 1000000000                                  /* how many numbers to produce */
pthread_mutex_t the mutex; 
pthread_cond_t condc, condp;                             /* used for signaling */
int buffer = 0;                                            /* buffer used between producer and consumer */

void *producer(void *ptr) {                              /* produce data */
    int i;

    for (i= 1; i <= MAX; i++) {
        pthread_mutex_lock(&the_mutex);                    /* get exclusive access to buffer */            
        while (buffer != 0) 
            pthread_cond_wait(&condp, &the_mutex);
        buffer = i;                                        /* put item in buffer */    
        pthread_cond_signal(&condc);                    /* wakeupconsumer */
        pthread_mutex_unlock(&the_mutex);                 /* release access to buffer */
      }
    pthread exit(0); 
}


void *consumer(void *ptr) {                             /* consume data */
    int i;
    for (i = 1; i <= MAX; i++) {
        pthread_mutex_lock(&the_mutex);                 /* get exclusive access to buffer */ 
        while (buffer ==0 ) 
            pthread_cond_wait(&condc, &the_mutex);        /* wakeupproducer */
        buffer = 0;
        pthread_cond_signal(&condp);  
        pthread_mutex_unlock(&the_mutex);                 /* release access to buffer */
    }
    pthread exit(0); 
}

int main(int argc, char **argv) {
    pthread_t pro, con;
    pthread_mutex_init(&the_mutex, 0); 
    pthread_cond_init(&condc, 0); 
    pthread_cond_init(&condp, 0); 
    pthread_create(&con, 0, consumer, 0); 
    pthread_create(&pro, 0, producer, 0); 
    pthread_join(pro, 0);
    pthread_join(con, 0);
    pthread_cond_destroy(&condc); 
    pthread_cond_destroy(&condp); 
    pthread_mutex_destroy(&the_mutex);
}

System calls and library functions

Thread Synchronization

Mutex

Pthread mutex can be used to protect data interfaces.

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Read-Write Lock

Read-write lock may have three states: lock state read mode, write mode locked state, an unlocked state.

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

Condition variable

When used with the condition variable mutex, allowing the thread to wait for non-competitive manner specified conditions occur. Condition itself is protected by a mutex, the thread must lock the mutex before changing the condition of the state.

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond,
                     pthread_mutex_t *restrict mutex);

int pthread_cond_signal(pthread_cond_t *cond);

Mutex pthread_cond_wait transmitted to the protection conditions. The caller mutex locked to the function, the calling thread function automatically placed on the list of threads waiting for the condition, and then the mutex is unlocked. This closed condition check and thread to sleep waiting for conditions to change the passage of time between these two operations, so the thread will not miss any changes in conditions. When pthread_cond_wait return, the mutex is locked again.

Spinlocks

Spin lock and mutex is similar, but it is not blocked by sleep the process, but has been in a busy-wait before obtaining a lock. Self-locking lock can be used in the following situations: the lock is held a short time, but threads do not want to spend too much money on re-scheduling.

Spin locks are usually used to implement the underlying primitive for other types of locks.

#include <pthread.h>

int pthread_spin_lock(pthread_spinlock_t *lock);

int pthread_spin_trylock(pthread_spinlock_t *lock);

int pthread_spin_unlock(pthread_spinlock_t *lock);

barrier

Barrier is a user synchronization mechanism to coordinate multiple threads in parallel work. Barrier allows each thread to wait until all the threads cooperation have reached a certain point, and then continue from that point.

#include <pthread.h>

int pthread_barrier_init(pthread_barrier_t *restrict barrier,
                        const pthread_barrierattr_t *restrict attr,
                        unsigned int count);

int pthread_barrier_wait(pthread_barrier_t *barrier);

Process synchronization and communication

pipeline

Pipeline is the oldest way of UNIX System IPC. Pipeline is half-duplex, can only be used between two processes have a common ancestor.

#include <unistd.h>

int pipe(int fd[2]);

PIPE_BUF constant predetermined pipeline kernel buffer size, if the number of bytes written conduit PIPE_BUF less, this operation does not communicate with other processes on the same cross-write pipeline.

FIFO

Through a process unrelated FIFO can also exchange data. Create a FIFO is similar to creating a file:

#include <sys/stat.h>

int mkfifo(const char *path, mode_t mode);

XSI IPC

Message queues, semaphores, and shared memory called XSI IPC.

And key identifier

IPC structures are each core with a non-negative integer identifier to be referenced. IPC internal identifier is the name of the object, each IPC objects associated with a key, the key name of the external object.

Authority structure

Each IPC structure is associated with a ipc_perm structure that defines the permissions and owners, including at least the following members:

struct ipc_perm {
    uid_t     uid;   /* owner's effective user id */
    gid_t     gid;   /* owner's effective group id */
    uid_t   cuid;  /* creator's effective user id */
    gid_t     cgid;  /* creator's effective group id */
    mode_t  mode;  /* access modes */
    ...
}

Advantages and disadvantages

XSI IPC is active in the system-wide, there is no reference count. For example, the contents of the message queue are not deleted after the quote process terminates. Compared with the pipeline, when the last reference to the pipeline process is terminated, the pipeline was completely removed. Another problem is that, XSI IPC structure with no name in the file system, the file system function can not be used to access or modify their properties, thus increasing the dozen new kernel system calls (msgget, semop, shmat, etc.) to support the IPC objects.

Message Queuing advantages are: reliable flow control.

Comparative semaphore, mutex locks and the recording

If a shared resource among multiple processes, you can use a semaphore, mutex lock or record to coordinate access.

If you use a semaphore, it first creates the amount of signal that contains a collection of members, the allocation of resources for the call sem_op of semop -1. Calls for the release of resources sem_op of semop +1. SEM_UNDO are specified for each operation, in order to deal with the case of the termination of the process under conditions of resources not released.

The use of record locking, then create an empty file, and use the first byte of the file (need not be present) to lock bytes. When the nature of record locking to ensure that when the lock holder terminates, the kernel will automatically release the lock.

The use of mutex, all the processes required to map the same file to the respective address space, using PTHREAD_PROCESS_SHARED mutex mutex initialization attribute of the file at the same offset. If a process does not release the mutex terminated to recovery will be very difficult.

The following figure shows on Linux, the use of these three different techniques for the time required for the operation of the lock. In each case, the resources are allocated, released one million times:

operating User Time system time clock
With the undo semaphore 0.50 6.08 7.55
Advisory record locking 0.51 9.06 4.38
Mutex shared storage 0.21 0.40 0.25

If we only need to lock to a single resource, XSI semaphores do not need all the bells and whistles, record locking semaphores than better, easier to use, faster, and when the process terminates system will manage the legacy of the lock. Unless specifically consider the performance, it will not use a mutex shared storage. First, use a mutex to restore the termination of the process is more difficult among multiple processes to share memory, and secondly, the process of sharing the mutex attribute has not been universal support.

POSIX semaphores

POSIX semaphore interfaces are intended to address several drawbacks XSI semaphore interfaces:

  • Achieve higher performance
  • Interface is simpler, there is no set semaphore
  • Deleted more perfect performance

Avoid using the message queues and semaphores, pipes should be considered full duplex and record locking, they are simpler to use.

Achieve in Linux

The kernel provides a set of synchronization implemented method, comprising an atomic operation, spin locks, semaphores, lock sequence.

Spinlocks

Spin locks can be used in interrupt context, and semaphores can not, because the semaphore might sleep. Use the lock interrupt processing, then you need to prohibit local interrupt, because otherwise it might lead to a deadlock and lock multiple applications.

The kernel provides a convenient way to disable interrupts and apply lock:

spin_lock_irqsave(&mr_lock, flags);
/* critical region ... */ 
spin_unlock_irqrestore(&mr_lock, flags);

spin_lock_irqsave save the current interrupt status, prohibiting local interrupt and get the spin lock.

In a single processor system, the spin lock just disable local interrupts, and to prohibit preemption.

The completion of variable (Completion Variables)

Completion Variables similar semaphores can be considered a simplified version of the semaphore. Completion Variables used in the kernel vfork system calls: When the child process to terminate the parent process through a notice Completion Variables.

Lock sequence (Sequential Locks)

Sequence provides a simple locking mechanism for shared read-write data, which is based on a sequence counter. When data is written, it will get a lock, and the sequence value increases. Before and after the data is read, the value read sequence. If the sequence values ​​are the same, then when reading data, and no write operation occurs. The further, if the value of the sequence is an even number, it indicates that a write operation is not currently in progress (due to the initial value 0, the write operation will both release the lock and to obtain a sequence value plus 1).

Order lock very lightweight, applies to the following scenarios:

  • Data has a lot of readers.
  • There are very few data writer.
  • Data are more inclined to write, read and does not allow write caused by hunger.
  • Data is very simple, but for some reason, you can not use atomic variables.

Uptime kernel memory read and write variables jiffies lock on the use of the order. Can not be read in some atoms 64 jiffies_64 system variables, using sequential read lock:

u64 get_jiffies_64(void) {
    unsigned long seq; 
    U64 right;

    do {
        seq = read_seqbegin(&xtime_lock);
        right = jiffies_64;
    } while (read_seqretry(&xtime_lock, seq)); 
    return ret;
}

Update jiffies_64 code is as follows:

write_seqlock(&xtime_lock); 
jiffies_64 += 1; 
write_sequnlock(&xtime_lock);

 

Transfer: http://masutangu.com/2016/11/27/linux-kernel-serial-2/

Guess you like

Origin www.cnblogs.com/zl1991/p/12013427.html