Inter-process descriptor transfer in Linux
Each process has its own independent process space, which makes the descriptor transmission between processes a little complicated. This is the content of high-level inter-process communication.
Descriptor transfer in Linux
In a Linux system, a child process automatically inherits the opened descriptor of the parent process. In actual applications, the parent process may need to pass the "opened descriptor" to the child process ", or the child process needs to be passed to the parent process; or the two processes may be irrelevant. Obviously, this requires a transmission mechanism.
To put it simply, you must first establish a Unix domain socket interface between the two processes as a channel for message transmission (using the socketpair function on the Linux system can easily establish a transfer channel ), then the sending process calls sendmsg to send a special message to the channel. The kernel will perform special processing on the message and then pass the opened descriptor to the receiving process.
The receiver then calls recvmsg to receive messages from the channel to obtain the opened descriptor. However, the actual operation is not as simple as it looks.
Here are some notes:
1 It should be noted that the pass descriptor does not pass an int-type descriptor number, but creates a new descriptor in the receiving process and in the file table of the kernel, it points to the same item as the descriptor sent by the sending process.
2. Any types of descriptors can be transferred between processes, such as the descriptors returned by functions such as pipe, open, mkfifo, socket, and accept, but not limited to sockets.
3. When a descriptor is being transmitted (from the sendmsg call to the recvmsg call), the kernel marks it as "in flight" (in flight ). During this period, even if the sender tries to disable the descriptor, the kernel will remain open for the receiving process. The sending descriptor will add 1 to the reference count.
4. the descriptor is sent through the auxiliary data (msg_control member of msghdr). At least one byte of data is always sent when the descriptor is sent and received, even if the data has no practical significance. Otherwise, when the receiving returns 0, the receiver will not be able to distinguish between "no data" (but the auxiliary data may have sockets) and "file terminator ".
5. The msg_control buffer of msghdr must be aligned with the cmghdr structure. We can see that the subsequent code implementation uses a union structure to ensure this.
Msghdr and cmsghdr struct
As mentioned above, the descriptor is sent through the msg_control Member of the msghdr struct. Therefore, before proceeding further, it is necessary to understand the msghdr and cmsghdr struct. Let's first look at msghdr.
struct msghdr { void *msg_name; socklen_t msg_namelen; struct iovec *msg_iov; size_t msg_iovlen; void *msg_control; size_t msg_controllen; int msg_flags; };
The structure members can be divided into the following four groups, which looks much clearer:
1 set of interface address members: msg_name and msg_namelen;
It is only required when the channel is a datagram set interface; msg_name points to the set interface address for sending or receiving information. Msg_namelen specifies the interface address length.
Msg_name points to the receiving address when recvmsg is called, and to the destination address when sendmsg is called. Note that msg_name is defined as a (void *) data type, so you do not need to convert the interface address display to (struct sockaddr *).
2 I/O vector references msg_iov and msg_iovlen
It is the actual data buffer. From the code below, we can see that one byte is handed over to it. This msg_iovlen is the number of msg_iov, not the length.
The msg_iov Member points to a struct iovec array. The iovc struct is defined in the SYS/uio. h header file. It has nothing special.
struct iovec { ptr_t iov_base; /* Starting address */ size_t iov_len; /* Length in bytes */ };
With iovec, you can use readv and writev functions to read or write multiple buffers in callback function calls. This is obviously more efficient than multiple read operations. The function prototypes of readv and writev are as follows:
#include <sys/uio.h> int readv(int fd, const struct iovec *vector, int count); int writev(int fd, const struct iovec *vector, int count);
3. msg_control and msg_controllen are affiliated data buffer members. The descriptor is sent through it. Later we will see that msg_control points to the affiliated data buffer, while msg_controllen indicates the buffer size.
4. The receiving information flag is msg_flags. Ignore
The cmsghdr structure is the turn. The affiliated information can include several independent affiliated data objects. Each object has a struct cmsghdr structure. The header is followed by the padding byte and then the object itself. Finally, more bytes may be filled before the next cmsghdr after the affiliated data object.
struct cmsghdr { socklen_t cmsg_len; int cmsg_level; int cmsg_type; /* u_char cmsg_data[]; */ };
The number of bytes of the Data affiliated to cmsg_len, which contains the size of the structure header. The 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, the affiliated data object is the file descriptor, scm_credentials, and the affiliated data object is a structure containing the certificate information );
The annotated cmsg_data is used to specify the location of the actual ancillary data to help you understand.
For cmsg_level and cmsg_type, We only care about sol_socket and scm_rights.
Msghdr and cmsghdr secondary macros
These structures are quite complex. The Linux system provides a series of macros to simplify our work. These macros can be transplanted between different UNIX platforms. These macros are described on the man manual page of cmsg (3:
#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 parameter: object size in the attached data buffer;
Calculate the size of the attached data in the cmsghdr header structure, including necessary fields. This value is used to set the cmsg_len Member of the cmsghdr object.
Cmsg_space () Macro
Input parameter: object size in the attached data buffer;
Calculate the size of the affiliated data added to the cmsghdr header structure, and fill in its fields and possible ending characters. Note that the cmsg_len () value does not include any possible ending padding characters. The cmsg_space () macro is useful for determining the size of the buffer required.
Note that if there are multiple ancillary data in the buffer, you must add multiple cmsg_space () macro calls at the same time to obtain the total space required.
The following example shows the differences between the two:
Printf ("cmsg_space (sizeof (short) = % d/N", cmsg_space (sizeof (short); // return 16 printf ("cmsg_len (sizeof (short )) = % d/N ", cmsg_len (sizeof (short); // return 14
Cmsg_data () Macro
Input parameter: pointer to the cmsghdr structure;
Returns the address of the first byte (if any) following the header and the child data after the padding. For example, when the descriptor is passed, the code will be in the following format:
Struct cmsgptr * cmptr;... int FD = * (int *) cmsg_data (cmptr); // send: * (int *) cmsg_data (cmptr) = FD;
Cmsg_firsthdr () Macro
Input parameter: pointer to the struct msghdr structure;
Returns the struct cmsghdr pointer to the First Affiliated object in the attached data buffer. If no ancillary data object exists, the returned pointer value is null.
Cmsg_nxthdr () Macro
Input parameter: pointer to the struct msghdr structure and to the current struct cmsghdr;
Returns the struct cmsghdr pointer of the next affiliated data object. If there is no next affiliated data object, the macro returns NULL.
These two macros can easily traverse all the affiliated data, as shown in the following form:
Struct msghdr msgh; struct cmsghdr * cmsg; For (cmsg = cmsg_firsthdr (& msgh); cmsg! = NULL; cmsg = cmsg_nxthdr (& msgh, cmsg) {// After obtaining cmmsg, you can use the cmsg_data () macro to obtain secondary data.
Functions sendmsg and recvmsg
The function prototype is as follows:
#include <sys/types.h> #include <sys/socket.h> int sendmsg(int s, const struct msghdr *msg, unsigned int flags); int recvmsg(int s, struct msghdr *msg, unsigned int flags);
The parameters are described as follows:
S, socket channel, for sendmsg is the sending socket, for recvmsg is corresponding to the receiving socket;
MSG, information header structure pointer;
Flags, an optional flag bit, which is the same as the flag of the send or sendto function call.
The Return Value of the function is the number of bytes actually sent/received. Otherwise, "-1" indicates that an error has occurred.
For more information, see the advanced I/O section of apue.
Now that you have finished the preparation, we are ready to start the topic.
Sending Descriptor
After the preparations, it is time to send the descriptor. Let's first look at the function prototype:
Int write_fd (int fd, void * PTR, int nbytes, int sendfd );
Parameters are described as follows:
@ FD: Send TCP socket interface. This can be the send socket interface returned by socketpair.
@ PTR: the buffer pointer for sending data;
@ Nbytes: number of bytes sent;
@ Sendfd: the descriptor sent to the receiving process;
The Return Value of the function is the number of written bytes. <0 indicates that sending fails;
To put it bluntly, the code goes first, and the code sending the descriptor is relatively simple. For details, see the comments in the code.
First, the old UNIX system uses the msg_accrights domain to pass the descriptor. Therefore, we need to use the macro have_msghdr_msg_control to support both versions.
Int write_fd (int fd, void * PTR, int nbytes, int sendfd) {struct msghdr MSG; struct iovec IOV [1]; // some systems use the old msg_accrights domain to pass the descriptor. in Linux, the new msg_control field # ifdef have_msghdr_msg_control Union {// As mentioned earlier, ensure that cmsghdr and msg_control are aligned with struct cmsghdr cm; char control [cmsg_space (sizeof (INT)];} control_un; struct cmsghdr * cmptr; // set the secondary buffer and length MSG. msg_control = control_un.control; MSG. msg_controllen = sizeof (control_un.control); // you only need a set of ancillary data. You can directly use cmsg_firsthdr to obtain cmptr = cmsg_firsthdr (& MSG); // you can specify required fields, data and length cmptr-> cmsg_len = cmsg_len (sizeof (INT); // The FD type is int, And the length cmptr-> cmsg_level = sol_socket; cmptr-> cmsg_type = scm_rights; // specify that the sent descriptor * (int *) cmsg_data (cmptr) = sendfd; // write FD to the secondary data # else MSG. msg_accrights = (caddr_t) & sendfd; // the old one is more convenient. MSG. msg_accrightslen = sizeof (INT); # endif // UDP is required, ignoring MSG. msg_name = NULL; MSG. msg_namelen = 0; // do not forget to set the data buffer. In fact, one byte is enough for IOV [0]. iov_base = PTR; IOV [0]. iov_len = nbytes; MSG. msg_iov = IOV; MSG. msg_iovlen = 1; return sendmsg (FD, & MSG, 0 );}
Receive Descriptor
After the sender is ready, the receiver is ready to receive the message. The function is prototype:
Int read_fd (int fd, void * PTR, int nbytes, int * recvfd );
Parameters are described as follows:
@ FD: receives the TCP socket interface, which can be the receiving socket interface returned by socketpair.
@ PTR: the buffer pointer for receiving data;
@ Nbytes: size of the receiving buffer;
@ Recvfd: used to receive the descriptor sent by the sending process;
The Return Value of the function is the number of bytes read. <0 indicates that reading failed;
The receiving function code is as follows, which is more complex than sending.
Int read_fd (int fd, void * PTR, int nbytes, int * recvfd) {struct msghdr MSG; struct iovec IOV [1]; int N; int newfd; # ifdef have_msghdr_msg_control Union {// align struct cmsghdr cm; char control [cmsg_space (sizeof (INT)];} control_un; struct cmsghdr * cmptr; // set the secondary data buffer and length MSG. msg_control = control_un.control; MSG. msg_controllen = sizeof (control_un.control); # else MSG. msg_accrights = (caddr_t) & newfd; // This simple MSG. msg_accrightslen = sizeof (INT); # endif // TCP ignores MSG. msg_name = NULL; MSG. msg_namelen = 0; // set the data buffer IOV [0]. iov_base = PTR; IOV [0]. iov_len = nbytes; MSG. msg_iov = IOV; MSG. msg_iovlen = 1; // The End Of The setting. Prepare to receive if (n = recvmsg (FD, & MSG, 0) <= 0) {return N ;} # ifdef have_msghdr_msg_control // check whether secondary data is received and the length. Recall the cmsg macro cmptr = cmsg_firsthdr (& MSG); If (cmptr! = NULL) & (cmptr-> cmsg_len = cmsg_len (sizeof (INT) {// It is necessary to check if (cmptr-> cmsg_level! = Sol_socket) {printf ("control level! = Sol_socket/N "); exit (-1);} If (cmptr-> cmsg_type! = Scm_rights) {printf ("control type! = Scm_rights/N "); exit (-1);} // Well, the descriptor is in this * recvfd = * (int *) cmsg_data (cmptr ));} else {If (cmptr = NULL) printf ("null cmptr, FD not passed. /n "); else printf (" message Len [% d] If incorrect. /n ", cmptr-> cmsg_len); * recvfd =-1; // descriptor was not passed} # else if (MSG. msg_accrightslen = sizeof (INT) * recvfd = newfd; else * recvfd =-1; # endif return N ;}
There are so many sending and receiving functions. As we can see above, inter-process socket transmission is still a little troublesome.
Original address: http://blog.csdn.net/sparkliang/article/details/5486069 (article changed)