Pass descriptors between processes

Each process has its own independent process space, which makes the transfer of descriptors between processes a bit complicated. This belongs to the content of advanced inter-process communication, which will be discussed below. By the way, let's talk about both Linux and Windows platforms.

Descriptor passing under Linux

Under the Linux system, the child process will automatically inherit the descriptors opened by the parent process. In practical applications, the parent process may need to pass the "post-opened descriptor" to the child process, or the child process needs to pass it to the parent process; or two Processes may be unrelated, which obviously requires a delivery mechanism.

To put it simply, first, a Unix domain socket interface needs to be established between the two processes as a channel for message passing (using the socketpair function on Linux systems can easily establish a transmission channel), and then the sending process calls sendmsg to send the message. The channel sends a special message, which the kernel will do special processing to pass the open descriptor to the receiving process.

The receiver then calls recvmsg to receive the message from the channel, resulting in an open descriptor. In practice, however, it is not as simple as it seems.

Let's take a look at a few points first:

1 It should be noted that passing the descriptor does not pass an int descriptor number, but creates a new descriptor in the receiving process, and in the kernel's file table, it points to the same as the descriptor sent by the sending process. item.

2 Any type of descriptor can be passed between processes, such as the descriptor returned by pipe , open , mkfifo or socket , accept and other functions, not limited to sockets.

3 As a descriptor is in transit (sent from the call to sendmsg to the receive from the call to recvmsg), the kernel will mark it as "in flight". During this time, even if the sender tries to close the descriptor, the kernel will keep it open for the receiving process. Sending a descriptor increments its reference count by 1.

4 Descriptors are sent through auxiliary data (the msg_control member of the structure msghdr). When sending and receiving descriptors, always send at least 1 byte of data, even if this data has no practical significance. Otherwise when the receive returns 0, the receiver won't be able to distinguish whether it means "no data" (but there may be sockets for auxiliary data) or "end of file".

5 In the specific implementation, the msg_control buffer of msghdr must be aligned with the cmghdr structure. It can be seen that the implementation of the following code uses a union structure to ensure this.

msghdr and cmsghdr structures

As mentioned above, the descriptor is sent through the msg_control member of the structure msghdr, so before continuing, it is necessary to understand the msghdr and cmsghdr structures, let's take a look at msghdr.

[cpp]  view plain copy  
  1. struct msghdr {  
  2.     void       *msg_name;  
  3.     socklen_t    msg_namelen;  
  4.     struct iovec  *msg_iov;  
  5.     size_t       msg_iovlen;  
  6.     void       *msg_control;  
  7.     size_t       msg_controllen;  
  8.     int          msg_flags;  
  9. };   

Struct members can be divided into the following four groups, which looks much clearer:

1 Socket address members msg_name and msg_namelen;

Required only if the channel is a datagram socket; msg_name points to the address of the socket on which to send or receive messages. msg_namelen specifies the length of the socket address.

msg_name points to the recipient address when calling recvmsg and to the destination address when calling sendmsg. Note that msg_name is defined as a (void *) data type, so there is no need to explicitly convert the socket address to (struct sockaddr *) .

2 I/O vector references msg_iov and msg_iovlen

It is the actual data buffer. As you can see from the code below, we give it 1 byte; this msg_iovlen is the number of msg_iov, not the length.

The msg_iov member points to an array of struct iovec, the iovc structure is defined in the sys/uio.h header file, it has nothing special.

[cpp]  view plain copy  
  1. struct iovec {  
  2.      ptr_t iov_base; /* Starting address */  
  3.      size_t iov_len; /* Length in bytes */  
  4. };  

With iovec, you can use the readv and writev functions to read or write multiple buffers in one function call, which is obviously more efficient than multiple read and write. The function prototypes of readv and writev are as follows:

[cpp]  view plain copy  
  1. #include <sys/uio.h>  
  2. int readv(int fd, const struct iovec *vector, int count);  
  3. int writev(int fd, const struct iovec *vector, int count);  

3 Auxiliary data buffer members msg_control and msg_controllen, through which descriptors are sent. As you will see later, msg_control points to the auxiliary data buffer, and msg_controllen indicates the buffer size.

4 Receive message flag bits msg_flags; ignored

 

Turning to the cmsghdr structure, the ancillary information can consist of several separate ancillary data objects. There is a struct cmsghdr structure before each object. After the header comes padding bytes, then the object itself. Finally, after the attached data object, there may be more padding bytes before the next cmsghdr.

[cpp]  view plain copy  
  1. struct cmsghdr {  
  2.     socklen_t cmsg_len;  
  3.     int       cmsg_level;  
  4.     int       cmsg_type;  
  5.     /* u_char     cmsg_data[]; */  
  6. };  

cmsg_len The number of bytes of ancillary data, this contains the size of the structure header, this value is calculated by the CMSG_LEN() macro;

cmsg_level indicates the original protocol level (for example, SOL_SOCKET);

cmsg_type indicates the type of control information (for example, SCM_RIGHTS, ancillary data objects are file descriptors; SCM_CREDENTIALS, ancillary data objects are a structure containing certificate information);

The commented cmsg_data is used to indicate the location of the actual ancillary data to aid understanding.

For cmsg_level and cmsg_type , at the moment we only care about SOL_SOCKET and SCM_RIGHTS .

msghdr and cmsghdr helper macros

These structures are quite complicated, and the Linux system provides a series of macros to simplify our work. These macros can be ported between different UNIX platforms. These macros are described by the cmsg(3) man page, so let's get to know them first:

#include <sys/socket.h>

struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);

struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);

size_t CMSG_ALIGN(size_t length);

size_t CMSG_SPACE(size_t length);

size_t CMSG_LEN(size_t length);

void *CMSG_DATA(struct cmsghdr *cmsg);

 

CMSG_LEN() macro

Input parameters: the size of the object in the attached data buffer;

Calculates the size of the cmsghdr header structure plus ancillary data, including the necessary alignment fields. This value is used to set the cmsg_len member of the cmsghdr object.

CMSG_SPACE() macro

Input parameters: the size of the object in the attached data buffer;

Calculate the cmsghdr header structure plus ancillary data size and include its fields and possible trailing padding characters, note that the CMSG_LEN() value does not include possible trailing padding characters. The CMSG_SPACE() macro is useful for determining the required buffer size.

Note that if you have multiple appendages in the buffer, be sure to add multiple CMSG_SPACE() macro calls at the same time to get the total space required.

The following example reflects the difference:

[cpp]  view plain copy  
  1. printf("CMSG_SPACE(sizeof(short))=%d/n", CMSG_SPACE(sizeof(short))); // 返回16  
  2. printf("CMSG_LEN(sizeof(short))=%d/n", CMSG_LEN(sizeof(short))); // 返回14  

CMSG_DATA() macro

Input parameters: pointer to cmsghdr structure;

Returns the address of the first byte (if any) of ancillary data following the header and padding bytes, such as when passing a descriptor, the code will be of the form:

[cpp]  view plain copy  
  1. struct cmsgptr *cmptr;  
  2. . . .  
  3. int fd = *(int *)CMSG_DATA(cmptr); // 发送:*(int *)CMSG_DATA(cmptr) = fd;  

CMSG_FIRSTHDR() 宏

Input parameters: pointer to the struct msghdr structure;

Returns a struct cmsghdr pointer to the first attached object within the attached data buffer. The returned pointer value is NULL if no attached data object exists.

CMSG_NXTHDR() macro

Input parameters: pointer to the struct msghdr structure, pointer to the current struct cmsghdr;

The struct cmsghdr pointer used to return the next ancillary data object, or NULL if there is no next ancillary data object.

With these two macros, it is easy to iterate through all the attached data, like the following form:

[cpp]  view plain copy  
  1. struct msghdr msgh;  
  2. struct cmsghdr *cmsg;  
  3. for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;  
  4.     cmsg = CMSG_NXTHDR(&msgh,cmsg) {  
  5.     // Once cmsg is obtained, auxiliary data can be obtained through the CMSG_DATA() macro   

Functions sendmsg and recvmsg

The function prototype is as follows:

[cpp]  view plain copy  
  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. int sendmsg(int s, const struct msghdr *msg, unsigned int flags);  
  4. int recvmsg(int s, struct msghdr *msg, unsigned int flags);  

The parameters of the two are described as follows:

s, the socket channel, which is the sending socket for sendmsg and the receiving socket for recvmsg;

msg , the pointer to the information header structure;

flags , optional flag bits, which are the same as the flags for send or sendto function calls.

The return value of the function is the actual number of bytes sent/received. Otherwise -1 is returned to indicate that an error occurred.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325992204&siteId=291194637