Linux provides a series of system calls that allow us to pass file descriptors between processes. Instead of simply passing the file descriptor, which is a 32-bit integer, we actually pass the file handle to the target process so that it can read and write to the file. Now suppose process B wants to send a file descriptor to process A. Let’s see how to do that.
1. UNIX domain socket
To pass file descriptors, you need to establish inter-process communication. UNIX domain socket is a way to communicate between processes, it is similar to a normal socket, except that it does not use an ip address, but a socket file. We want to use it to send control messages and thus pass file descriptors. First we will have process A create a socket:
Note the arguments to the socket
function. AF_UNIX
specifies the protocol family as a UNIX domain socket. Here we use the datagram socket SOCK_DGRAM
, which is similar to UDP and does not require a link to be established, but is sent directly through the address. Of course, you can also use SOCK_STREAM
, which is similar to TCP. We won’t list them here.
Next we bind the address:
Note that the address here is no longer an ip address, but a file path. Here we specify the address as process_a
, and call bind
to bind the address, which creates a socket file called process_a
.
This way, other processes can send messages to this process through a special address like process_a
. It is the same as sending UDP messages, except that struct sockaddr_un
is used to define the address instead of struct sockaddr_in
or struct sockaddr_in6
. In addition, it is important to note that other processes can pass file descriptors to this process by sending control messages.
2. sendmsg
Linux provides a pair of system calls: sendmsg
and recvmsg
. Unlike our usual send
and recv
, they can be used to send or receive control messages in addition to regular data, which is the key to passing file descriptors; they can also be used to send or receive a discrete piece of data.
Let’s look at the declarations of these two system calls
sendmsg
and recvmsg
use a structure struct msghdr
to describe the data sent. The structure is defined as follows:
|
|
msg_name
Destination address. This is optional, if the protocol is connection-oriented, you don’t need to specify an address; otherwise, you need to specify an address. This is similar tosend()
andsendto()
.msg_iov
the data to be sent. This is an array, the length of which is specified bymsg_iovlen
, and the elements of the array are astruct iovec
structure that specifies the starting address (iov_base
) and length (iov_len
) of a contiguous piece of data. In other words, it can send more than one continuous piece of data; or, it can send a discontinuous piece of data.msg_control
controls the message. This is what we are talking about today. We can’t set it directly, we have to use a series of macros to set it.
msg_control
points to a sequence consisting of a struct cmsghdr
structure and its additional data. The definition of struct cmsghdr
is as follows:
struct cmsghdr
actually defines the header of the data, which should be followed by an unsigned char
array that holds the actual data of the control information. This is often referred to as a variable-length structure
. msg_control
points to a sequence of such variable-length structures. The memory structure is shown in the figure below:
We need to use the following macros:
CMSG_FIRSTHDR()
returns the first element of the sequence pointed to bymsg_control
CMSG_SPACE()
The length of the actual data of the incoming control message, returning the space required for the variable-length structureCMSG_LEN()
The length of the actual data of the control message, and the length of the variable-length structure.CMSG_DATA()
Returns the first address of the actual data that holds the control information.
Note the difference between
CMSG_SPACE()
andCMSG_LEN()
: the former contains the length of the padding, which is the actual space occupied; the latter does not contain the length of the padding, and is used to assign the value tocmsg_len
.
Next, let’s have process B pass a file descriptor to process A. First set the address of process A, which is “process_a”:
We only need to send control messages, not regular data, so we leave the regular data empty:
Next, we allocate space for the control data, since we only pass one file descriptor, so the length is sizeof(int)
:
Then you can set the struct msghdr
structure:
Next we get struct cmsghdr
and set it:
Finally, just send it out:
3. recvmsg
Now we ask process A to receive the file descriptor passed to it. Here we call recvmsg
.
|
|
Thus, process A receives the file descriptor from process B, and process B opens the file and can perform read and write operations on it.