Essays on UNP Volume II

I. overview   there are two branches of interprocess communication mentioned in this book, posix and system V. most...

I. overview

  there are two branches of interprocess communication mentioned in this book, posix and system V. most linux systems today support these two branches.
There are obvious differences between the interprocess communication interfaces provided by posix and system V, which are described in Chapter 2 and Chapter 3.

  there are three ways to communicate between processes:
     ① file system. Through read, write and other functions, through the kernel, the sharing between processes is realized on the file system. Of course, this requires some synchronous operation. This type of persistence is based on when the file is deleted.
     ② kernel. A < interprocess communication object > > established on the kernel. Different processes transfer information between processes by calling a set of related functions. Its persistence will not be deleted with the end of the process. This object will disappear only when the display calls a deletion operation or the kernel restarts. For example, posix message queue (I just saw this today).
     ③ process. Such objects are created by the process and disappear as the process disappears, such as sockets, anonymous pipes, famous pipes, etc.

     persistence depends on the IPC (interprocess communication, hereinafter referred to as IPC) of the kernel:
       posix message queue
       posix named semaphore
       posix shared memory
       message queue of system V
       system V semaphore
       system V shared memory.
     the persistence of other IPC methods is dependent on the process.

      also mentioned about the impact of fork, exec, exit and other functions on various IPC objects. It is roughly the same as the idea in the book. Since you want to fork or exec, you should fork them at the beginning of the program rather than after making a pile of things.

     mention errno again. It is implemented using thread specific data. Each thread has an independent errno. Errno has an error value of 0.

II. Some parameter values used when creating posix IPC objects

  the following parameters are used by posix IPC, such as posix message queue, posix semaphore, posix shared memory and other values that should be passed when these objects are created.
  whether it is message queue, semaphore or shared memory, as long as o is specified when creating or opening_ Creat, for example, needs to specify the mode of its subsequent posix semaphore_ Parameter of type T. (see the corresponding chapter of posix for details)
    here is just a description of the values of these parameters. For how to use them, see below.

  oflag parameter
    mq_ The open column   corresponds to the parameters that can be used when creating posix message queue
    sem_ The open column   corresponds to the parameters that can be used when creating posix semaphores
    shm_ The open column   corresponds to the parameters that can be used when creating posix shared memory

  mode_ Parameter of type T

posix needs a name or path when it is created, but it will have portability problems. This custom function is used to solve them.

#define POSIX_IPC_PREFIX "/" char *px_ipc_name(const char *name) { char *dir, *dst, *slash; if ( (dst = malloc(PATH_MAX)) == NULL) return(NULL); if ( (dir = getenv("PX_IPC_NAME")) == NULL) { #ifdef POSIX_IPC_PREFIX dir = POSIX_IPC_PREFIX; /* from "config.h" */ #else dir = "/tmp/"; /* default */ #endif } slash = (dir[strlen(dir) - 1] == '/') ? "" : "/"; snprintf(dst, PATH_MAX, "%s%s%s", dir, slash, name); return(dst); /* caller can free() this pointer */ }
III. some parameter values used when creating system V IPC objects

  system V is different from posix's IPC creation.
   the creation interface provided by posix IPC. The oflag parameter indicates how to create or open the subsequent mode_ The T parameter indicates the rwx permissions of the object.
The creation interface provided by   system V IPC aggregates how to create, open, rwx permissions, etc. into the oflag parameter. (see the corresponding chapter of system V for details)
    here is just a description of the values of these parameters. For how to use them, see below.

  oflag parameter
     System V does not provide read-write permission, because as long as the process that opens the system V IPC object has the corresponding rwx permission, it will obtain the corresponding permission. There is only IPC here_ CREAT,IPC_EXCL and the corresponding rwx permission combination.
     the first figure is the same as posix. The second figure describes the corresponding permission values of different system V IPC objects. These values are specified by bit or in the oflag parameter.

  each system V IPC object will have an IPC associated with it_ Perm structure.

struct ipc_perm { uid_t uid; // Owner user id gid_t gid; // Owner group id uid cuid; // Create user id gid_t cgid; // Create the group id of the user mode_t mode; // read-write permission ulong_t seq; // Slot serial number? key_t key; };
Four pipes and FIFO

  1. Pipeline
     about pipeline, it can only be used between processes with parent-child relationship, and it is a one-way data flow. Use the int pipe(int fd[2]) function to return a pair of file descriptors through the output parameters. fd[0] is used for reading and fd[1] is used for writing. Several examples are used in the book to describe its use. The parent process establishes two pairs of such pipes. After fork, the parent and child processes close one pair of read descriptors and the other pair of write descriptors respectively to form a full duplex channel. The details are not described here, mainly focusing on FIFO.

FILE * popen(const char*command, const char *type);
int plose(FILE * stream);
      the above two functions are another application of pipeline. In the former, the shell program executes the command line of its first parameter, creates another process, and establishes a pipeline between the calling process and the created process. The type parameter 'r' represents that the calling process reads the output of the command, and 'w' represents that the calling process writes the input of the command.

2.FIFO
     FIFO is also known as famous pipeline. It is a file that exists in a path on the system.

     use FIFO (see code below):
In a process, you call the mkfifo function to create a file, and use the open function to open it in the current process.
        ② another process also uses the open() function to open this file, so as to establish a famous pipeline.
         ③ after the communication ends, both processes call close to close the descriptor opened with open. Although as long as one side of the pipe is closed, the read on the other side will return 0 accordingly, indicating the end of the file, it feels like there is always a sense of ceremony when it is closed,
        ④ one of the processes calls unlink to delete the file created with mkfifo.

     examples and other supplements:
int mkfifo(const char *pathname, mode_t mode);
       mkfifo specifies O by default_ Create | excl, which either creates a new FIFO or returns EEXIST error (errno).
       if a process opens FIFO for reading, but no process opens FIFO for writing, the read operation is blocked. Of course, non blocking can also be set.
       FIFO is also unidirectional. To establish a full duplex channel, you need to establish two FIFOs.

      OPEN_MAX: pipeline and FIFO are also descriptors. This constant is the maximum number of descriptors that can be opened by a process at the same time
      PIPE_BUF: the original saying in the book is "the maximum amount of data that can be written to the pipeline or FIFO atomically", but literally, it is more like the size of the buffer. Of course, it is not.

       if the amount of data requested to be read is less than the amount of data in FIFO, only available data is returned.
       if the number of bytes written is less than PIPE_BUF, atomic writing is guaranteed, but it cannot be guaranteed beyond.
       if it is non blocking:
         the number of bytes written is less than PIPE_BUF
           there is enough space to write.
            there is not enough space. errno is set to EAGAIN.
         the number of bytes written is greater than PIPE_BUF
             how much space to write, and the number of words written is used as the write return value.
            there is no one byte space, that is, FIFO is full, and errno is set to EAGAIN.

        the book also describes an example. The example is that process A establishes A well-known FIFO path. Process A reads it, and any other process can open it and write information through open (it also shows that multiple processes can write to one FIFO). The focus here is not the example itself, but A small detail:
{
           process A reads and opens the FIFO once to generate A description, and writes and opens it once to generate another write descriptor. Although this write descriptor has never been used, it has the value of existence.
           assuming that A already exists but does not open the write descriptor, when another process writes, opens and then closes the FIFO, the read descriptor of A will return 0, which means that the file ends, and the read descriptor in A will become invalid. The existence of the write descriptor in A avoids such behavior (returning 0). Perhaps behind it is A behavior similar to the reference count. As long as the write count is 0, the end of the file is returned (0).
}

       finally, there is a custom protocol header on FIFO, just like the application layer of TCP. Naturally, there is no need to say more about this general technique.

#define FIFO1 "/tmp/fifo.1" #define FIFO2 "/tmp/fifo.2" #define FIFE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) / / user read, user write, group read and other read process A Core code of if( (mkfifo(FIFO1, FILE_MODE) < 0) && (errno != EEXIST) ) { printf("If mkfifo Something went wrong, but errno no EEXIST,that mkfifo The creation failed because the file already exists"); exit(); } if( (mkfifo(FIFO2, FILE_MODE) < 0) && (errno != EEXIST) ) { printf("The error is the same as above, but if it fails here, pay attention to delete it FIFO1 Create a new file."); unlink(FIFO1); exit(); } int readfd = open(FIFO1, O_RDONLY, 0); int writefd = open(FIFO2, O_WRONLY, 0); ... close(readfd); close(writefd); ... unlink(FOFO1); // Follow the principle of who creates and who deletes unlink(FOFO2); process B Core code of int writefd = open(FIFO1, O_WRONLY, 0); // Note that FIFO1 is written here, and process A is read to FIFO1. int readfd = open(FIFO2, O_RDONLY, 0); ... close(readfd); close(writefd);
Five posix message queues

  this chapter first introduces how to use posix message queue, then introduces how to use signal to realize asynchronous notification to message queue, also describes real-time signal and reviews the implementation of reliable signal model in Chapter 10 of apue, and finally gives an implementation code of message queue.

  1. Use of POSIX message queue

MQ_OPEN_MAX The number of message queues that a process can open at the same time MQ_PRIO_MAX Message maximum priority value The message queue descriptor is returned successfully, and the message queue descriptor is returned in case of failure-1 mqd_t mq_open(const char * name, int oflag, mode_t mode, struct mq_attr * attr); name: Is a path to open or create, such as/temp.1234 oflag: yes O_RDONLY,O_WRONLY,O_RDWR One of the three, and then press bit or O_CREAT,O_EXECL,O_NONBLOCK,See Chapter II for details mode: See Chapter II for details attr: See below for details mq_getattr and mq_setattr function mode and attr Is optional, not empty, but optional mq_open('Pathname', O_RDONLY | NONBLOCK) As long as you specify O_CREAT,You need to specify subsequent mode and attr parameter mq_open The function can create a message queue object, open a message queue object, and create and open a message queue object 0 is returned for success and 0 is returned for failure-1 int mq_close(mqd_t mqdes); mq_open If you open an object, you should naturally close it 0 is returned for success and 0 is returned for failure-1 int mq_unlink(const char * name); Delete from the kernel according to the pathname mq_open Created IPC object Message queues are created with mq_open Increases the reference count as mq_close Reduce references Call if the reference count is 0. unlink,He will wait until the count becomes 0 to delete the IPC struct mq_attr { long mq_flags; // Queue flag, mainly non blocking long mq_maxmsg; // Maximum number of messages allowed in the queue long mq_msgsize; // Maximum length of a single message long mq_curmsgs; // The current number of messages in the queue } 0 is returned for success and 0 is returned for failure-1 int mq_getattr(mqd_t mqdes, struct mq_attr *attr); int mq_setattr(mqd_t mqdes, const struct mq_attr *attr, struct mq_attr *oattr); get Is to obtain the information of the message queue, set That's the setting set In, old information will be oattr Return in In addition, set Should only be used to set or clear the non blocking flag because mq_msgsize and mq_maxmsg Should only be set when created mq_curmsgs It's the current quantity. Naturally, there's no need to say more 0 is returned for success and 0 is returned for failure-1 int mq_send(mqd_t mqdes, const char * ptr, size_t len, unsigned prio); The number of bytes in the message is returned successfully, and the number of bytes in the message is returned in case of failure-1 ssize_t mq_receive(mqd_t mqdes, char * ptr, size_t len, unsigned prio); These two functions respectively put or take a message from a queue The first three parameters indicate which message queue, message itself, message length and buffer length The fourth parameter is priority, MQ_PRIO_MAX See the beginning of this chapter mq_receive The message with the highest priority is always returned, and the same priority is first in first out.

   2. Use mq_notify and signal to realize message notification

0 is returned for success and 0 is returned for failure-1 int mq_notify(msq_t mqdes, const struct sigevent * notification); If notification If it is not empty, it indicates that the current process wants to receive notifications from the queue If notification If it is empty, it means that the current process does not want to(cancel)Receive notifications from queues Only one process can be registered to receive notifications at a time Only when (the queue changes from empty to non empty) && No mq_receive The notification will only be issued As long as one process is calling mq_receive,His priority is the highest mq_notify The registration effect of is only once. After receiving a notification, you must call the registration again struct sigevent { int sigev_notify; // Can be int sigev_signo; // The value should be a specific signal value, such as SIGPIPE and SIGUSR1 union sigval sigev_value; // The last three parameters process notifications by starting a thread, // sigev_notify is SIGEV_THREAD, see the following example. void (*sigev_notify_function)(union sigval); pthread_attr_t *sigev_notify_attributes; }; union sigval { int sival_int; void *sival_ptr; };

example:

Example of signal mode----------------- int n; int signal; sigset_t newmask; struct sigevent sigev; mqd_t mqd = mq_open("Pathname", O_RDONLY | NONBLOCK); sigempty(&newmask); // Signal set empty sigaddset(&newmask,SIGUSR1); // Add GIGUSR1 signal to the signal set, which is provided by the system for free use by users sigprocmask(SIG_BLOCK, &newmask, NULL); // Block this signal sigev.sigev_notify = SIGEV_SIGNAL; // Fill in this structure sigev.sigev_signo = SIGUSR1; // When the queue changes from empty to non empty, SIGUSR1 signal is sent mq_notify(mqd, &sigev); // Registration notice for(;;) { sigwait(&newmask, &signo); // Wait for the SIGUR1 signal to appear if(signo == SIGUR1) // If so, signo == SIGUR1 { mq_notify(mqd, &sigev); // Register again and send SIGUSR1 signal after receiving the notice while( 0 <= (n = mq_receive(mqd, A buffer buff, Length of buffer, NULL)) ) printf("%d %s", n, buff); if(errno != EAGAIN) // while keep reading, know mq_receive returns a value of < = 0 exit(); // errno is EAGAIN, which means no message is readable, but if it is not EAGAIN, it indicates that other errors have occurred } } Examples of threading----------------- The Liezi in the book should start this thread when receiving the notification In this thread, it is used again mq_notify Registration notice, and mq_notify The parameter of once again contains "start this thread when receiving notification" struct sigevent sigev; // global variable ... { sigev.sigev_notify = SIGEV_THREAD; // Note SIGEV_THREAD sigev.sigev_value.sival_ptr = NULL; // Member sigev_value as sigev_ notify_ Parameters of function sigev.sigev_notify_function = notify_thread; // Which function to start as a thread sigev.sigev_notify_attributes = NULL; // Thread property related settings mq_notify(mqd, &sigev); // Register to receive the notification, and start the thread function processing after receiving the notification } void notify_thread(union sigval arg) // This function is user-defined { int n; char * buff[mq_attr.mq_msgsize]; // mq_attr.mq_msgsize can be accessed via mq_getattr get mq_notify(mqd, &sigev); // Register again and start the thread when notified while( 0 <= (n = mq_receive(mqd, buff, Length of buffer, NULL)) ) printf("%d %s", n, buff); if(errno != EAGAIN) // while keep reading, know mq_receive returns a value of < = 0 exit(); // errno is EAGAIN, which means no message is readable, but if it is not EAGAIN, it indicates that other errors have occurred pthread_exit(NULL); }

  3. Summary of unix signal model
     I was confused for a long time when I looked here. I picked up apue and looked at the signal chapter.
     summary:
       it is unsafe to directly use the signal function provided by the system. Possible problems:
               signal function can set a signal handler for a signal. apue original words < there is a time window between the signal occurrence and the call of the signal handler. During this time period, another interrupt signal may occur, and the second signal will execute the default action >, which is about that the same signal is generated twice in a short time, The second signal occurs before the first signal is processed. The second signal will call the default behavior instead of the signal handler, but the default action of most signals is to end the program.

       solution:
           ① directly shield and block the signal. When I know that the signal will occur, but there will be no serious consequences, I will solve the person who raises the problem
           ② use the sigaction function, which will also set a signal handler for the signal, but before calling the signal handler, it will mask the blocking signal, then call the signal handler, and then remove the blocking after processing. In this way, if the same signal is generated again in the time window mentioned above, it will be blocked, After unblocking, the signal is delivered to the process, and then you can go back to the beginning of this paragraph for another processing.
         ③ set SIG_IGN. This method is just a guess, because I'm not sure whether the behavior of ignoring signals is defined as a signal handler. Just write casually. The first two are enough.

  4. Real time signal
     therefore, the values of real-time signals are between SIGRTMIN and SIGRTMAX.
     real time signals are queued, generated many times, submitted many times, and first in first out.
     for the signal between SIGRTMIN and SIFRTMAX, SIGRTMIN will be delivered first than SIGRTMIN+1, and the signal with small signal value will be given priority.
     to use real-time signals, specify SA in the sigaction call of a process receiving signals_ Siginfo logo.

     the use of real-time signals is also based on sigaction();

struct sigaction act, oact; // The sigaction structure is defined in section apue10.14 act.sa_sigaction = func; // func is the handler called when that signal occurs act.sa_flags = SA_SIGINFO; // This must be set to use real-time signals act.sa_mask = mask; // mask is a sigset_t type // sigemptyset and sigaddset can be used to add a signal to this mask // When the signal identified by the sigaction function occurs, the mask blocks all signals in this mask sigaction(signo, &act, &oact); // Call sigaction to bind the signal to the corresponding behavior (act). -------------------------------------------------------------------------------------- sigqueue()Function is a function of real-time signal. It can send a signal to a process and carry a sigval As information, the following is an example ----- //These three lines send a real-time signal to a process and carry an integer union sigval val; val.sival_int = 999; sigqueue(pid, SIGRTMIN+1, val); // How do I take this integer? first sigaction.sa_sigaction The type of this member is void(int signo, siginfo_t * info, void *context); It can then be used in the signal handler info->si_value.sival_int This value is an integer of 999 -----

One of the 8195 november3, 2021 13:19:40   Chapter 1, 4 and 5

Vi. V message queue

  system V message queue also exists with the kernel, and it also needs to go through the process of create open close delete.
  as long as the IPC object is still alive, the data inserted will continue until someone reads it.
  each system V message queue is associated with a struct msqid_ds structure.

struct msqid_ds { struct ipc_perm msg_perm; // This structure is defined in Chapter 3 struct msg *msg_first; // First message in queue struct msg *msg_last; // Last message in queue msglen_t msg_cbytes; // The total number of words in the current queue msgqnum_t msg_qnum; // Number of messages in the current queue msglen_t msg_qbytes; // Maximum number of bytes allowed for the queue pid_t msg_lspid; // Process id of the last call to msgsnd() pid_t msg_lrpid; // Process id of the last call to msgrcv() time_t msg_stime; // The last time msgsnd() was called time_t msg_rtime; // Time of the last call to msgrcv() time_t msg_ctime; // Time of the last call to msgctl() }; Successfully returned one key_y Type, error returned-1 key_t ftok(const char * pathname, int id); This function returns a key_t Type object as the information needed to create a message queue This function puts pathname Parameters and id Parameters and some other information are combined to generate a key_t type A non negative identifier is returned successfully, and an error is returned-1 int msgget(key_t key, int oflag); Return value: unique in the whole system. It is used to identify the unique value of a message id,Also used as msgsnd msgrcv msgctl Parameters of ket: Creating a message queue requires key_t The value of type is explicitly written in the book ftok Function generation is one way IPC_PRIVATE This constant can also be used as key_t Parameter passing ensures the creation of new and unique objects, IPC_PRIVATE It is process independent and can be used between different processes to create multiple processes IPC in addition key The examples in the book directly use long integers as parameters, but the way to use pathnames is relatively extensive and in line with the rules oflag: Read / write permission bit, put in msqid_ds.msg_perm.mode See Chapter III for details. 0 is returned successfully and 0 is returned in error-1 int msgsnd(int msqid, const void * ptr, size_t length, int flag); msqid: Indicates which message queue it is ptr: Is the sent information, and the following template should be followed: struct msgbuf { long mtype; // The mtype member must be greater than 0. See the following msgrcv function for the meaning of its value char mtext[1]; // It can be understood as a char *, which indicates the location where we want to send data // You can customize a structure, protocol, or other on the basis of mtext // Its length should be indicated by the parameter length }; length: In particular, he is not prt The length of the whole block, but from mtext The length of the data starting at, excluding that mtype Or sizeof(msgbuf)-sizeof(long) flag: Can be 0 or IPC_NOWAIT,The latter is non blocking"); There is not enough space to return in non blocking mode, errno by EAGIN In blocking mode, there is space to wait, or the message queue is blocked msgctl delete(errno:EIDRM),Interrupted errn by EINTR The data length of the read buffer is returned successfully, and an error is returned-1 ssize_t msgrcv(int msqid, void * ptr, size_t length, int flag); msqid: Indicates which message queue it is ptr: Receive buffer, msgsnd of ptr What did you send, msgrcv Take back what intact, including which mtype member length: yes prt The size of the indicated buffer flag: Parameters can be IPC_NOWAIT perhaps MSG_NOERROR In a blocking situation, of course, you wait for the desired message to arrive In the non blocking case, there is no desired message errno by ENOMSG,Here, the desired news is about mtype of MSG_NOERROR:When the data to be received is larger than the buffer, set this bit: truncate the data, not set: E2BIG error mtype Description of: type Is 0: According to the first in first out principle, each message is returned in order. Why msgsnd of mtype Reason why member cannot be 0 type Greater than 0: return ptr.mtype by type My first message type Less than 0: return ptr.mtype The message with the lowest value, and ptr.mtype Should be less than or equal to type Take absolute value 0 is returned successfully and 0 is returned in error-1 int msgctl(int msqid, int cmd, struct msqid_ds *buff); msqid: Indicates which message queue it is cmd: Three commands are available: IPC_RMID: Delete the message queue, and the messages on the queue are discarded, buff Parameter ignore IPC_SET: Set up the following four members: msqid_ds.msg_perm.uid, msqid_ds.msg_perm.gid, msqid_ds.msg_perm.mode, msqid_ds.msg_qbytes. IPC_STAT: stay buff Returns the information associated with the message queue msqid_ds Structure.

One of the 8195 November5, 2021 21:24:17   Chapter 6

789

  chapter 78 is about threads, such as mutex locks, conditional variables, read-write locks and their implementation. It is better to use the C + + standard library for these.
    Chapter 9 is about fcntl. It locks the document records and explains the use of fcntl in detail. However, fcntl itself is unsafe. Whether it is advisory locking or mandatory locking, it may lead to inconsistent data.

Ten posix semaphores

   there are two types of posix semaphores, one is posix famous semaphore, and the other is posix shared memory semaphore.
   the usage or operation mode of these two semaphores (or the function of operating these two semaphores) is the same, but there are some differences in creation from other places.
     posix named semaphore: persistence follows the kernel. The object itself is created and initialized by the system and returns its pointer.
     posix shared memory semaphore: persistence < at least > follow the process (actually follow the persistence of shared memory). The object itself is created by the user and passed to the system for initialization.
     see below for detailed differences.

  SEM_NSEMS_MAX     the maximum number of semaphores that a process can open at the same time
  SEM_VALUE_MAX    maximum value of semaphore value itself

----Creation and use of well-known semaphores The pointer to the semaphore is returned successfully, and the error is returned SEM_FALIED sem_t * sem_open(const char * name, int oflag, mode_t mode, unsigned int value); name: On which path was it created or opened oflag: If specified O_CREAT,Then you need to specify the subsequent mode and value,See also Chapter II oflag The parameter specifies the read-write mode by default, because the operation on the semaphore is the addition and subtraction of itself mode: Permission bits described in Chapter 2 value: Is the initial value of the semaphore, which is generally 1 and cannot exceed SEM_VALUE_MAX Success is 0, error is-1 int sem_close(sem_t * sem); When the process terminates, the open semaphore is automatically closed, or the reference count is reduced by one Shutdown also does not result in deletion, as it continues with the kernel Even if no process opens a semaphore, the value in this object will stay in the kernel Success is 0, error is-1 int sem_unlink(const char * name); Remove the object from the kernel (when the reference count is 0) Success is 0, error is-1 int sem_wait(sem_t * sem); int sem_trywait(sem_t * sem); If the value of the semaphore is greater than 0, subtract it by one and return, otherwise wait The latter is a non blocking version. If you can't subtract one to return, it will be rejected errno(EAGAIN) Success is 0, error is-1 int sem_post(sem_t * sem); Original words: increase the semaphore value by one, and then wake up any process waiting for the semaphore value to become positive The second half of the sentence here is a little confused. First, the book shows a situation: On different systems, the semaphore value of some systems can become negative, and the lowest semaphore value of some systems can only be 0 Obviously, a system that can represent a negative number indicates how many processes are currently waiting for semaphores Determine whether the system can represent a negative number, and the subsequent values can be used sem_getvalue()function The book also shows another situation: Create a semaphore with an initial value of 0 and call it twice sem_wait() The semaphore value becomes-2,Then call again sem_post(),The semaphore value becomes-1 When First call sem_wait()Your program continues to run (junction)When there are several processes in sem_wait()Wait: A system in which the semaphore value can be negative: The semaphore value is negative: when the semaphore value is increased by one, a waiting process is awakened, but the semaphore value will not be reduced by one Systems where semaphore values cannot be negative: Semaphore value is 0: When the semaphore value is added by one, a waiting process is awakened, and the semaphore value is followed sem_wait()Call minus one Success is 0, error is-1 int sem_getvalue(sem_t * sem, int * valp); Returns the current value of a semaphore ----Creation of shared memory semaphores Some notes on shared memory semaphores: For shared memory semaphores, it shall be guaranteed and only once sem_init()Call, the behavior of multiple calls is undefined. In addition to creating and destroying, the use method, like the famous semaphore, is called sem_wait(),sem_post()etc. Its persistence mentioned earlier at least follows the process, but in fact, the semaphore is valid only if the shared memory area containing this semaphore remains valid There is also a puzzle about shared memory semaphores, which is the same system V The semaphore has problems, but the latter has solutions: A semaphore is first placed in shared memory, where it is sem_init()front, Because of the competition between different processes, other processes use this semaphore. What should we do? Maybe there will be a solution when you see the chapter on shared memory. Or, you should call sem_init(),Put it in shared memory? Error return-1,No description. Success is 0 int sem_init(sem_t * sem, inst shared, unsigned int value); sem: The parameter should be a space allocated by the user and passed to the parameter shared: Is 0: The semaphore is shared by threads in the same process Not 0(Generally 1): Shared by different processes, but this semaphore must be shared in memory, Processes that want to use the semaphore also need access to the shared memory Because the shared memory in this book is in the following chapters, the examples in the book are thread based, that is shared Is 0 value: Is the initial value of the semaphore Success is 0 and error is returned-1 int sem_destroy(sem_t * sem); There's nothing to say. Create and destroy accordingly

One of the 8195 november8, 2021 11:26:32   Chapter 10

Eleven V semaphores

   the interface provided by posix semaphore is for the operation of a single semaphore, but the situation is different on system V.
  system V continues with the kernel.
The interface provided by   system V is about a semaphore set. There are several semaphores in a semaphore set, and a set corresponds to a semid_ds structure (see below).
   several structures of system V semaphores throughout this chapter:

struct semid_ds // Structure corresponding to semaphore set { struct ipc_perm sem_perm; // The structure is described in Chapter 3 struct sem * sem_base; // This is an array. Each element corresponds to a specific semaphore. The structure is shown below. ushort sen_nsems; // Length of array time_t sem_otime; // The last time the semop() function was called on the collection time_t sem_ctime; // The time of creation, or the last time IPC was used_ Set flag the time when the semctl() function was called }; struct sem // Structure corresponding to a single semaphore { ushort_t semval; // Value of semaphore short sempid; // The process id of the last successful call to the semop() function ushort_t semncnt; // The number of threads waiting for semval to become greater than a certain value. Where is a certain value stored? See semop for details ushort_t semzcnt; // Number of threads waiting for semval to become 0 ---- For each semaphore object, there is also an implicit member, which is semadj,This member is not in this structure It is maintained by the kernel, and this member is only called semop()And specified SEM_UNDO Flag will be modified about SEM_UNDO and semadj The role of in semop()Description in ---- }

   system V only provides three functions, but the three functions themselves carry a large amount of information. Write them separately here:
   semget function:

A non negative identifier is returned successfully, and an error is returned-1 int semget(key_t key, int nsems, int oflag); Return value: The returned identifier is passed to semop and semctl Which semaphore set represents is unique within the system. key: This can be done by ftok Function. nsems: The number of semaphores in the set, semid_ds.sen_nsems member oflag: (read)SEM_R,(Modify write)SEM_A,IPC_CREAT,IPC_EXCL Or chapter three rwx jurisdiction semget The function will correspond to semid_ds Structural<gross>Member initialization, sem_base Each concrete element in the array is not initialized This requires a call semctl Function to initialize each member, that is, the initialization of a signal set needs to be divided into two steps The first step is semget Create, the second step is semctl Initializing each signal element creates a race: process A Created a signal set S,call semget After that, the call is made. semctl Before, the process B Open signal set S,Then call it semop. resolvent: semget When creating a signal set, ensure that semid_ds.sem_otime Is initialized to 0, and the member is the last successful call semop Time of day. process A Medium: semget->semctl->semop,Last time semop You can't do anything process B Medium: semget->Continuous testing semid_ds.sem_otime Becomes non-0, becomes non-0, then A yes semctl It must be done About how to get semid_ds.sem_otime Value of, in semctl Description in function

   semop function:

Success is 0 and error is returned-1 int semop(int semid, struct sembuf * opsptr, size_t npos); semid: from semget The identifier returned, indicating which signal set it is opsptr: One sembuf Can operate on multiple elements at the same time npos: opsptr Length of array because opsptr Is an array that specifies npos Actions. If this npos If any of the operations cannot be completed, then opsptr Array npos None of the operations will be performed. So one way to use it might be to specify it every time you call it npos For 1, complete one operation so that it will not affect others. --------------------------------------------------------------------------- struct sembuf { short sem_num; // Used as semid_ds.sem_base, indicating which semaphore element it is short sem_op; // What to do? See the following for details short sem_flag; // 0,IPC_NOWAIT,SEM_UNDO, a non blocking flag, as for SEM_UNDO flag, described later in this paragraph --- about sembuf Structure to ensure that there are these three members, but there may be other members, and the memory distribution may be different So you have to use sembuf.sem_num = 999;Assign values to members in this way. --- }; semop The use of is almost all gathered in sembuf Description of the value of the member. sembuf.sem_op Greater than 0: <Release resources sembuf.sem_op The value of is added to semval Come on. Like: semid_ds.sem_base[sembuf.sem_num].semval += sembuf.sem_op; sembuf.sem_op Equal to 0: Hope to wait semval Becomes 0. If it is already 0, return immediately. If not semzcnt+=1,Blocking wait, blocking return semzcnt-=1. sembuf.sem_op Less than 0: <Apply for resources, hope to wait semval Becomes greater than or equal to sembuf.sem_op Value of. If already greater than or equal to sembuf.sem_op The absolute value of, then from semval Subtract from sembuf.sem_op Absolute value of. If less than sembuf.sem_op The absolute value of, semncnt+=1,So blocking, when returning semncnt-=1,Also from semval Subtract from sembuf.sem_op Absolute value of. IPC_NOWAIT: Non blocking flag If this flag is not specified and resources cannot be obtained, blocking may be interrupted, resulting in errno==EINTR When this flag is specified, failure to obtain resources will result in errno==EAGAIN. SEM_UNDO: At the beginning of this chapter struct sem It is mentioned in the structure semadj The existence of. This sign is in sembuf.sem_op Used when not 0. semadj The existence of is attached to the signal set, but its instance object corresponds to each process. Whenever a process opens a signal set, there will be a corresponding signal in the kernel semid_ds.sem_base[i]of semadj appear. whenever semop Operation and specified SEM_UNDO When, regardless of sembuf.sem_op Whether it is greater than 0 or less than 0 will cause sembuf.sem_op The value of is added to the corresponding semadj. For example: semadj Initial 0 for the first time semop cause semval+=5,semadj+=5,here semadj For 5 The second time semop cause semval-=3,semadj-=3,here semadj For 2 ...... The first n This causes semadj by n When the final procedure is concluded, whether it is exit,Abnormal termination or others will lead to semval-=n If semadj Greater than 0, then semval Minus an integer, "equivalent to applying for resources" If semadj Less than 0, then semval Minus a negative number, "equivalent to releasing resources" semval Changed back to the value at the beginning of the program. Summary: imagine a process with a very short life cycle, which is simply called several times semop Then it's over. Called in this process semop Also specified SEM_UNDO Apply for resources, and then the procedure ends, System pass semadj The value of helps us return the resources instead of giving them to us semop Pass a value greater than 0 sembuf.sem_op Manual return SEM_UNDO The sign is to help us avoid semop The process of returning or recovering resources

   semctl function:

A non negative value is returned after success. 0 means success. An error is returned-1 int semctl(int semid, int semnum, int cmd, union semun arg); Return value: according to cmd Different parameters semid: from semget The identifier returned, indicating which signal set it is semnum: be used as semid_ds.sem_base An index indicating which semaphore element it is cmd: A fixed set of constant values. See below for details arg: A consortium, according to cmd Different values use different members union semun { int val; cmd Value is SETVAL When using struct semid_ds *buf; cmd Value is IPC_SET,IPC_STAT When using ushort *array; cmd Value is GETALL,SETALL When using ----The consortium needs to be declared by the user, and should: semctl(semid, 0, XXX, args); }args; cmd Constant value of: GETVAL: semval Return as return value SETVAL: hold semval The value is set to arg.val,If the operation is successful, the existing one in all processes will be deleted semadj Set to 0 GETNCNT: semncnt Return as return value GETZCNT: semnznt Return as return value GETPID: sempid Return as return value GETALL: The return value itself is 0, through arg.array Pointer returns the value of all semaphores in a signal set semval Value, arg.array The array pointed to is user allocated and long enough SETALL: Set the of all signals semval Value, by arg.array appoint IPC_SET: adopt arg.buf,set up semid_ds.sem_perm.uid,semid_ds.sem_perm.gid,semid_ds.sem_perm.mode. sem_ctime It will also become the current time. IPC_STAT: adopt arg.buf,Returns the of the semaphore set semid_ds Structure, which can be obtained semid_ds.sem_otime. IPC_RMID: take semid The specified semaphore is deleted from the system.

One of the 8195 17:54:00, November 9, 2021   Chapter 11

12. File mapping of shared memory

There is an independent address space between the parent and child processes of   fork, but the mapping relationship of mmap will be inherited after fork.

The starting address of the mapped area is returned successfully. The error is MAP_FAILED void * mmap(void * addr, size_t len, int prot, int flags, int fd, off_t offset); addr: Specify an address where you want the starting address of the mapped object to be addr Yes, but not guaranteed len: In the program, the starting address from the return value, the length of this space, and the same file can map up to so many bytes prot: Read and write permissions for this area (mapping area). PROT_READ,PROT_WRITE,PROT_EXEC,PROT_NONE,They are read-write execution and inaccessible. flag: MAP_SHARED Changes are shared, and modifications will also modify files, but when to write them back is a problem msync Function. MAP_PRIVATE Changes are private. Changes will only be made to the copy in memory and will not be synchronized to the file MAP_FIXED Accurate explanation addr Parameter. I can't understand what it means. Because of portability, this should not be specified MAP_SHARED and MAP_PRIVATE One of them must be specified, fd: It should be an open file descriptor, but it should not be a terminal or socket. Such objects must pass through write perhaps read. In addition, when mmap After successful opening, this fd It won't affect being closed, maybe mmap It is also an opening of reference counting. offset: from fd At the beginning of the associated descriptor, the offset number of bytes is mapped into memory. Success is 0 and error is returned-1 int munmap(void * addr, size_t len); addr: mmap The address returned len: Mapping area address length Deletes a mapping relationship from the address space Success is 0 and error is returned-1 int msync(void * addr, size_t len, int flags); take addr Starting at, len Length bytes are written to the file. addr: Can be mmap The returned value can also be a subset of it, so only part of it will be written back len: flags: MS_ASYNC Asynchronous write, call the function with this value, and return immediately if the write operation has been suspended to the kernel. MS_SYNC Synchronous write, call the function with this value, and block until the write is completed MS_INVALIDATE Invalidate the cached data, or so the in memory copies inconsistent with the file will be invalidated and then synchronized. MS_ASYNC and MS_SYNC One of them must be specified,

  this chapter also shows some. For example, at the beginning of the mapped fd, write a structure, and then mmap returns addr. You can convert addr to this type of structure and directly operate its members. Also, use lseek to offset the file pointer, and then write an empty byte at this position, and the file will expand to the specified length.
  in addition, it also focuses on the interaction between the memory mapping length and the length of the file itself.
     first of all, it should be clear that there are three lengths.
     ① the length of the file itself.
     ② length related to page file.
     ③ length of memory mapping space when mmap is used.
     when ① > = ③, assuming that the file itself is 5000 bytes and the page size is 4096 bytes, the 5000 byte file requires two pages (0-8191, 8192 bytes in total). In the program, these bytes are accessible, but the partial read-write behavior exceeding 5000 bytes is invalid. If it exceeds the range of 8191, SIGSEGV will be caused and a segment error will be caused, Then the program ends.
     when ① (5000) < ③ (10000), the file size and page size are the same as before. Similarly, there will be no error in accessing the range of 0-8191. SIGBUS signal will be generated when accessing 8191-9999, and SIGSEGV signal will be generated when accessing the address after 9999.

One of the 8195 November10, 2021 7:37:40   Chapter 12

XIII posix shared memory

  similarities and differences between posix shared memory and memory mapping:
     memory mapping: open the file through open, return a descriptor, and then map it to the address space by mmap.
     posix shared memory: through shm_open creates or opens a path and returns a descriptor, which is mapped to the address space by mmap.
     although open and SHM_ The descriptors returned by open are all descriptors, but they are different in the subsequent ftruncat functions. The descriptor of the former is regarded as an ordinary file, and the descriptor of the latter is regarded as a shared memory object.

The non negative descriptor is returned successfully, and the error is returned-1 int shm_open(const char * name, int oflag, mode_t mode); Return value: a file descriptor, which is then used to mmap name: A pathname to open or create oflag: Open or create, see Chapter 2 mode: Document rwx Authority, see Chapter II 0 is returned successfully and 0 is returned in error-1 int shm_unlink(const char * name); Delete the object corresponding to the pathname when the reference count is 0 0 is returned successfully and 0 is returned in error-1 int ftruncate(int fd, off_t length); fd: descriptor If it's an ordinary file( open Open: File length ratio length Large, redundant parts are discarded. File length ratio length Small, whether the file is extended is undefined. A portable extension file method: lseek reach length-1 At, write an empty byte. If it is a shared memory object( shm_open Open): set the size of the object to length byte length: How long does the file extend 0 is returned successfully and 0 is returned in error-1 int fstat(int fd, struct stat * buf); fd: A file descriptor, which mainly says shm_open The descriptor returned buf: When fd By shm_open When returned, only the values of four members are valid: st_mode,st_uid,st_gid,st_size This is mainly achieved through st_size To determine the length of shared memory.

   there is also a competition here. After creating a shared memory object, its default length is 0. If two processes create and open one, but the length is not set in time after creation, the open process can only judge that the length becomes a non-0 value through fstat.

  ftruncate or mmap first?
                      8195.

1V shared memory

  similarities and differences between poisx and system V shared memory:
    posix: shm_open is created or opened, mmap is mapped to the address space, and the size of the shared memory area can be adjusted at any time.
     system V: shmget is created or opened, and shmat is mapped to the address space. However, in system V, this mapping process is called attachment. The shared memory area size can only be set at creation time.

Each shared memory object is associated with an object struct shmid_ds { struct ipc_perm shm_perm; // Chapter 3, members of each system V IPC size_t shm_segsz; // Length of shared memory area pid_t shm_lpid; // The last process to operate on shared memory pid_t shm_cpid; // Creator process shmatt_t shm_nattch; // Number of currently mapped (attached) shmat_t shm_cnattch; // In core # attached time_t shm_atime; // Time of last attachment time_t shm_dtime; // The time of the last separation, perhaps the release of attachment? time_t shm_ctime; // The last time the current structure was changed, shmid_ds }; The shared memory object is returned successfully, and an error is returned-1 int shmget(key_t key, size_t size, int oflag); Return value: with system V Message queues and semaphores are unique in the system. key: Can be ftok perhaps IPC_PRIVATE,see system V Message queuing chapter oflag: Create or open and rwx Authority, chapter III. The starting address of the mapping area is returned successfully, and an error is returned-1 void * shmat(int shimd, const void * shmaddr, int flag); shmid: shmget Return value of shmaddr: Null pointer: returned by the address selected by the system Non null pointer: establishes a mapping relationship on the specified address flag: SHM_RDONLY read-only access SHM_RND cause shmaddr The non empty address specified by the parameter is aligned down to one SHMLBA Constant value of Success 1 is 0, failure returns-1 int shmdt(const void * shmaddr); shmaddr: shmat The mapping address returned This function is used to unmap Success 1 is 0, failure returns-1 int shmctl(int shmid, int cmd, struct shmid_ds * buff); shmid: shmget Which shared memory object cmd: IPC_RMID Unmap and remove the shared memory from the system IPC_SET adopt buff Parameter setting shmid_ds.shm_perm.uid,shmid_ds.shm_perm.gif,shmid_ds.shm_perm.mode IPC_STAT adopt buff Returns the of the shared memory shmid_ds structure buff: buffer

One of the 8195 November10, 2021 18:02:36   Chapter 13 and 14

Fifteen single host remote procedure calls

The   gate provides an interface for one process to call another process on a host.
  the caller is called the client and the callee is called the server.
   the remote procedure call of a single machine mainly has two functions:
     ① pass some data to the server for processing, and then return
     ② pass open descriptors between the server and the client

   the following is a simple process. See the following for details.
     server:
      ①door_create creates a door and associates which process function will be executed through the door
        ② fatach attaches the door to a pathname (Association)
       ③ fdetach cancellation of attachment
      ④door_revoke undo the door, or delete the door
    customer:
       ① use open to open the path associated with fatach
      ②door_call gate and required parameters, a remote procedure call occurs, waiting for the server dorr_return, door_return calls should be placed in procedure functions.
       ③ close the file.

  one of the client or server terminates in the gate procedure call:
     if the server terminates before the client, the client's door_call will cause EINTR error.
    door_ The call may also be interrupted by a signal, resulting in an EINTR error.
     if in door_ When the call is blocked, the client is terminated, and the call will be sent to the server to cancel the door executed by the thread pool_ server_ Porc thread signal, if this thread can be canceled, then cancel it, then call the corresponding thread cleanup function. But these threads cannot be cancelled by default. They will finish executing, door_return results are discarded.
     when door_ If the call is interrupted, should it be called again? For the customer, he needs a result return. For the server, whenever there is a door_call, the server will have a thread started and executed. Whether the re call behavior is safe or not should depend on whether the user-defined process function is safe or not.

This structure is used in passing descriptors struct door_desc_t { door_attr_t d_attributes; // DOOR_DESCRI[TOR Describes the descriptor of the union transfer representation // DOOR_RELEAS After the descriptors are passed, these passed descriptors are automatically closed. Otherwise, the user needs to handle which descriptors himself union // It is a consortium, but there is only one member. May it be for future scalability? { struct { int d_descriptor; // The descriptor to pass door_id_t d_id; // The book only says that this member is the "unique id", and the example does not fill in this member. Maybe it is door_info_t.di_uniquifier? }d_desc; }d_data; }; Success is 0 and error is returned-1 int door_call(int fd, door_arg_t * argp); Because there are door_arg_t This structure is convenient to explain door_create fd: use open Descriptor returned when opening a pathname. argp: struct door_arg_t { char *data_ptr; // The data area when passing data to the server procedure call can be explained by defining a structure on this basis size_t data_size; // Length of data_ptr door_desc_t *desc_ptr; // The structure used when passing descriptors is one data, because multiple descriptors can be passed at the same time size_t desc_num; // Number of pass descriptors char *rbuf; // Buffer for the return value of the server size_t rsize; // Length of return value buffer }; When passing a data procedure call, desc_ptr and desc_num Generally null And 0. Similarly, when passing descriptors, data_ptr and data_size Generally null And 0. Of course, it's not impossible to use it at the same time. door_call The call is synchronous and blocked, and it will wait until the server returns. to data_ptr,desc_ptr,rbuf It is also possible to specify the same address, door_call When calling, this address saves parameters, and after returning, this address saves results. door_call When returning, no matter what the original value is, its data_ptr and desc_ptr Will be pointed to door_call When called rbuf Your address, no why. If there is no data result or descriptor transfer result, then data_size or desc_num Is 0. If rbuf If the length of cannot accommodate the result of the server, it will lead to a gate call mmap To allocate a new buffer This buffer needs to be called by the user munmap To return to the system So monitoring rbuf Whether members are changed has also become the responsibility of users A nonnegative descriptor (a gate) is returned successfully, and an error is returned-1 int door_create(Door_server_proc * proc, void * cookie, u_int attr); proc: A function pointer when the client calls door_call,This function will be called. Users should customize this function. Its prototype is: typedef void Door_server_porc( void * cookie, char * dataptr, size_t datasize, door_desc_t * descptr, size_t ndesc); among cookie Parameter is door_create The second parameter of is automatically passed when called The remaining four parameters are door_arg_t The first four members of the structure, door_call The transferred data is obtained here and then processed in this function His return value is void,Not that we should not return, but that we should call it manually inside this function door_call return. cookie: I didn't understand the meaning, and the example in the book has always been 0. attr: 0 Or the following two constant values are bitwise OR DOOR_PRIVATE Whenever there is a door_call Request, the server starts a thread to call Door_server_porc When the value is not specified, it will be taken from the thread of the process, otherwise the door will need to have its own thread pool and invoke it. DOOR_UNREF When the descriptor referring to the gate is reduced from 2 to 1, Door_server_porc of dataptr Will be specified as DOOR_UNREF_DATA Then call them in turn, like Door_server_porc(0, DOOR_UNREF_DATA, 0, 0, 0); door_create The returned descriptor counts as a reference to the door. fattach The attachment of is also a reference to the door. customer open It's also a reference to the door. A call occurs whenever the reference count drops from 2 to 1, regardless of the sequence of invalidations of these references DOOR_LOCAL This procedure is only used for this process DOOR_REVOKE Called door_revoke Success is 0 and error is returned-1 int door_revoke(int d); d: door_create Return value of Undoing a door, or deleting a door, is like closing a descriptor. Success is not returned to the caller, but to door_call Error returned-1 int door_return(char * dataptr, size_t datasize, door_desc_t * descptr, size_t * ndesc); Return data result usage dataptr and dattasize,Used when passing descriptors descptr and ndesc. Similarly, when using one group, the other group is null And 0. There's no problem using them at the same time. 0 is returned successfully and 0 is returned in error-1 int fattach(int fildes, const char *path); fildes: door_create Return value of path: The pathname and door_create Create a door association so that customers can pass through open The pathname gets the descriptor and is then aligned door_call call 0 is returned successfully and 0 is returned in error-1 int fdetach(const char *path); Disassociate a path from a door 0 is returned successfully and 0 is returned in error-1 int door_cred(door_cred_t * cred); cred: The service obtains the customer information and returns it through this parameter This function should be Door_server_porc This is called the server process that we customize. struct door_cred_t { uid_t dc_euid; // Valid customer id gid_t dc_egid; // Valid customer group id uid_t dc_ruid; // Actual customer id gid_t dc_rgid; // Actual customer group id pid_t dc_pid; // Customer process id }; 0 is returned successfully and 0 is returned in error-1 int door_info(int fd, door_info_t * info); fd: customer open The descriptor you opened info: This parameter returns information about the server struct door_info_t { pid_t di_target; // Server process id door_ptr_t di_proc; // The address of the Door_server_porc function in the service process, but different address spaces are meaningless door_ptr_t di_data; // cookie parameters during door creation door_attr_t di_attributes; // Door related attributes DOOR_PRIVATE, DOOR_UNREF, DOOR_LOCAL, DOOR_REVOKE door_id_t di_uniquifier; // Unique system wide value assigned to each door when it is created }; door_server_create; door_bind; door_unbind; These three functions allow the user to manage the thread pool of the gate server.

One of the 8195 november11, 2021 20:44:21   Chapter 15

Remote procedure call between hosts

11 November 2021, 21:13 | Views: 1971

Add new comment

For adding a comment, please log in
or create account

0 comments