linux进程间文件描述符传递,文件描述符在进程间的传递实例:FD passing for DRI

211cae1fefebf4aa0b64bfd913a48b6f.png

点击上方蓝字关注Linuxer

Using the DMA-BUF interfaces to pass DRI objects between the client

and server, as discussed in my previous blog posting on DRI-Next,

requires that we successfully pass file descriptors over the X

protocol socket.

Rumor has it that this has been tried and found to be difficult, and

so I decided to do a bit of experimentation to see how this could be

made to work within the existing X implementation.

(All of the examples shown here are licensed under the GPL, version 2

and are available from git://keithp.com/git/fdpassing)

Basics of FD passing

The kernel internals that support FD passing are actually quite simple

-- POSIX already require that two processes be able to share the same

underlying reference to a file because of the semantics of the fork(2)

call. Adding some ability to share arbitrary file descriptors between

two processes then is far more about how you ask the kernel than the

actual file descriptor sharing operation.

In Linux, file descriptors can be passed through local network

sockets. The sender constructs a mystic-looking sendmsg(2) call,

placing the file descriptor in the control field of that

operation. The kernel pulls the file descriptor out of the control

field, allocates a file descriptor in the target process which

references the same file object and then sticks the file descriptor in

a queue for the receiving process to fetch.

The receiver then constructs a matching call to recvmsg that provides

a place for the kernel to stick the new file descriptor.

A helper API for testing

I first write a stand-alone program that created a socketpair, forked

and then passed an fd from the parent to the child. Once that was

working, I decided that some short helper functions would make further

testing a whole lot easier.

Here's a function that writes some data and an optional file

descriptor:ssize_t

sock_fd_write(int sock, void *buf, ssize_t buflen, int fd)

{

ssize_t     size;

struct msghdr   msg;

struct iovec    iov;

union {

struct cmsghdr  cmsghdr;

char        control[CMSG_SPACE(sizeof (int))];

} cmsgu;

struct cmsghdr  *cmsg;

iov.iov_base = buf;

iov.iov_len = buflen;

msg.msg_name = NULL;

msg.msg_namelen = 0;

msg.msg_iov = &iov;

msg.msg_iovlen = 1;

if (fd != -1) {

msg.msg_control = cmsgu.control;

msg.msg_controllen = sizeof(cmsgu.control);

cmsg = CMSG_FIRSTHDR(&msg);

cmsg->cmsg_len = CMSG_LEN(sizeof (int));

cmsg->cmsg_level = SOL_SOCKET;

cmsg->cmsg_type = SCM_RIGHTS;

printf ("passing fd %d\n", fd);

*((int *) CMSG_DATA(cmsg)) = fd;

} else {

msg.msg_control = NULL;

msg.msg_controllen = 0;

printf ("not passing fd\n");

}

size = sendmsg(sock, &msg, 0);

if (size < 0)

perror ("sendmsg");

return size;

}

And here's the matching receiver function:ssize_t

sock_fd_read(int sock, void *buf, ssize_t bufsize, int *fd)

{

ssize_t     size;

if (fd) {

struct msghdr   msg;

struct iovec    iov;

union {

struct cmsghdr  cmsghdr;

char        control[CMSG_SPACE(sizeof (int))];

} cmsgu;

struct cmsghdr  *cmsg;

iov.iov_base = buf;

iov.iov_len = bufsize;

msg.msg_name = NULL;

msg.msg_namelen = 0;

msg.msg_iov = &iov;

msg.msg_iovlen = 1;

msg.msg_control = cmsgu.control;

msg.msg_controllen = sizeof(cmsgu.control);

size = recvmsg (sock, &msg, 0);

if (size < 0) {

perror ("recvmsg");

exit(1);

}

cmsg = CMSG_FIRSTHDR(&msg);

if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {

if (cmsg->cmsg_level != SOL_SOCKET) {

fprintf (stderr, "invalid cmsg_level %d\n",

cmsg->cmsg_level);

exit(1);

}

if (cmsg->cmsg_type != SCM_RIGHTS) {

fprintf (stderr, "invalid cmsg_type %d\n",

cmsg->cmsg_type);

exit(1);

}

*fd = *((int *) CMSG_DATA(cmsg));

printf ("received fd %d\n", *fd);

} else

*fd = -1;

} else {

size = read (sock, buf, bufsize);

if (size < 0) {

perror("read");

exit(1);

}

}

return size;

}

With these two functions, I rewrote the simple example as follows:void

child(int sock)

{

int fd;

char    buf[16];

ssize_t size;

sleep(1);

for (;;) {

size = sock_fd_read(sock, buf, sizeof(buf), &fd);

if (size <= 0)

break;

printf ("read %d\n", size);

if (fd != -1) {

write(fd, "hello, world\n", 13);

close(fd);

}

}

}

void

parent(int sock)

{

ssize_t size;

int i;

int fd;

fd = 1;

size = sock_fd_write(sock, "1", 1, 1);

printf ("wrote %d\n", size);

}

int

main(int argc, char **argv)

{

int sv[2];

int pid;

if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) < 0) {

perror("socketpair");

exit(1);

}

switch ((pid = fork())) {

case 0:

close(sv[0]);

child(sv[1]);

break;

case -1:

perror("fork");

exit(1);

default:

close(sv[1]);

parent(sv[0]);

break;

}

return 0;

}

Experimenting with multiple writes

I wanted to know what would happen if multiple writes were made, some

with file descriptors and some without. So I changed the simple example

parent function to look like:void

parent(int sock)

{

ssize_t size;

int i;

int fd;

fd = 1;

size = sock_fd_write(sock, "1", 1, -1);

printf ("wrote %d without fd\n", size);

size = sock_fd_write(sock, "1", 1, 1);

printf ("wrote %d with fd\n", size);

size = sock_fd_write(sock, "1", 1, -1);

printf ("wrote %d without fd\n", size);

}

When run, this demonstrates that the reader gets two bytes in the

first read along with a file descriptor followed by one byte in a

second read, without a file descriptor. This demonstrates that

a file descriptor message forms a barrier within the socket; multiple

messages will be merged together, but not past a message containing a

file descriptor.

Reading without accepting a file descriptor

What happens when the reader isn't expecting a file descriptor? Does

it just get lost? Does the reader not get the message until it asks

for the file descriptor? What about the boundary issue described above?

Here's my test case:void

child(int sock)

{

int fd;

char    buf[16];

ssize_t size;

sleep(1);

size = sock_fd_read(sock, buf, sizeof(buf), NULL);

if (size <= 0)

return;

printf ("read %d\n", size);

size = sock_fd_read(sock, buf, sizeof(buf), &fd);

if (size <= 0)

return;

printf ("read %d\n", size);

if (fd != -1) {

write(fd, "hello, world\n", 13);

close(fd);

}

}

void

parent(int sock)

{

ssize_t size;

int i;

int fd;

fd = 1;

size = sock_fd_write(sock, "1", 1, 1);

printf ("wrote %d without fd\n", size);

size = sock_fd_write(sock, "1", 1, 2);

printf ("wrote %d with fd\n", size);

}

This shows that the first passed file descriptor is picked up by the

first sock_fd_read call, but the file descriptor is closed. The second

file descriptor passed is picked up by the second sock_fd_read call.

Zero-length writes

Can a file descriptor be passed without sending any data?void

parent(int sock)

{

ssize_t size;

int i;

int fd;

fd = 1;

size = sock_fd_write(sock, "1", 1, -1);

printf ("wrote %d without fd\n", size);

size = sock_fd_write(sock, NULL, 0, 1);

printf ("wrote %d with fd\n", size);

size = sock_fd_write(sock, "1", 1, -1);

printf ("wrote %d without fd\n", size);

}

And the answer is clearly "no" -- the file descriptor is not passed

when no data are included in the write.

A summary of resultsread and recvmsg don't merge data across a file descriptor message boundary.

failing to accept an fd in the receiver results in the fd being

closed by the kernel.

a file descriptor must be accompanied by some data.

Make X pass file descriptors

I'd like to get X to pass a file descriptor without completely

rewriting the internals of both the library and the X server. Ideally,

without making any changes to the existing code paths for regular

request processing at all.

On the sending side, this seems pretty straightforward -- we just need

to get the X connection file descriptor and call sendmsg directly,

passing the desired file descriptor along. In XCB, this could be done

by using the xcb_take_socket interface to temporarily hijack the

protocol as Xlib does.

It's the receiving side where things are messier. Because a bare read

will discard any delivered file descriptor, we must make sure to use

recvmsg whenever we want to actually capture the file descriptor.

Kludge X server fd receiving

Because a passed fd creates a barrier in the bytestream, when the X

server reads requests from a client, the read will stop sending data

after the message with the file descriptor is consumed.

Of course, this process consumes the passed file descriptor, and if

that call isn't made with recvmsg set up to receive it, the fd will be

lost.

As a simple kludge, if we pass a meaningless fd with the X request and

then the 'real' fd with a following XNoOperation request, the existing

request reading code will get the request, discard the meaningless fd

and then stop reading at that point due to the barrier. Once into the

request processing code, recvmsg can be called to get the real file

descriptor and the associated XNoOperation request.

I wrote a test for this that demonstrates how this works:static void

child(int sock)

{

uint8_t xreq[1024];

uint8_t xnop[4];

uint8_t req;

int i, reqlen;

ssize_t size, fdsize;

int fd = -1, *fdp;

int j;

sleep (1);

for (j = 0;; j++) {

size = sock_fd_read(sock, xreq, sizeof (xreq), NULL);

printf ("got %d\n", size);

if (size == 0)

break;

i = 0;

while (i < size) {

req = xreq[i];

reqlen = xreq[i+1];

i += reqlen;

switch (req) {

case 0:

break;

case 1:

if (i != size) {

fprintf (stderr, "Got fd req, but not at end of input %d < %d\n",

i, size);

}

fdsize = sock_fd_read(sock, xnop, sizeof (xnop), &fd);

if (fd == -1) {

fprintf (stderr, "no fd received\n");

} else {

FILE    *f = fdopen (fd, "w");

fprintf(f, "hello %d\n", j);

fflush(f);

fclose(f);

close(fd);

fd = -1;

}

break;

case 2:

fprintf (stderr, "Unexpected FD passing req\n");

break;

}

}

}

}

int

tmp_file(int j) {

char    name[64];

sprintf (name, "tmp-file-%d", j);

return creat(name, 0666);

}

static void

parent(int sock)

{

uint8_t xreq[32];

uint8_t xnop[4];

int i, j;

int fd;

for (j = 0; j < 4; j++) {

/* Write a bunch of regular requests */

for (i = 0; i < 8; i++) {

xreq[0] = 0;

xreq[1] = sizeof (xreq);

sock_fd_write(sock, xreq, sizeof (xreq), -1);

}

/* Write our 'pass an fd' request with a 'useless' FD to block the receiver */

xreq[0] = 1;

xreq[1] = sizeof(xreq);

sock_fd_write(sock, xreq, sizeof (xreq), 1);

/* Pass an fd */

xnop[0] = 2;

xnop[1] = sizeof (xnop);

fd = tmp_file(j);

sock_fd_write(sock, xnop, sizeof (xnop), fd);

close(fd);

}

}

Fixing XCB to receive file descriptors

Multiple threads may be trying to get replies and events back from the

X server at the same time, which means the kludge of having the real

fd follow the message will likely lead to the wrong thread getting the

file descriptor.

Instead, I suspect the best plan will be to fix XCB to internally

capture passed file descriptors and save them with the associated

reply. Because the file descriptor message will form a barrier in the

read stream, xcb can associate any received file descriptor with the

last reply in the read data. The X server would then send the reply

with an explicit sendmsg call to pass both reply and file descriptor

together.

Next steps

The next thing to do is code up a simple fd passing extension and try

to get it working, passing descriptors back and forth to the X

server. Once that works, design of the rest of the DRM-Next extension

should be pretty straightforward.

往期精彩文章>>>

...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux上,使用DBus传递文件句柄需要以下步骤: 1. 定义DBus消息的格式,包含传递文件句柄的FD(File Descriptor)类型。 2. 在发送方进程中打开文件,并将文件句柄通过DBus消息发送给接收方进程。 3. 在接收方进程中接收DBus消息,并从消息中获取文件句柄。 4. 使用文件句柄进行文件操作,并在使用完成后关闭文件句柄。 以下是一个简单的C++代码示例,演示了如何使用DBus传递文件句柄: ```cpp #include <iostream> #include <dbus/dbus.h> #include <fcntl.h> #include <unistd.h> using namespace std; // 定义DBus消息的格式 static const char* DBUS_MSG_PATH = "/com/example/Test"; static const char* DBUS_MSG_INTERFACE = "com.example.TestInterface"; static const char* DBUS_MSG_NAME = "TestMessage"; static const char* DBUS_MSG_CONTENT = "This is a test message."; // 定义文件路径 static const char* FILE_PATH = "/tmp/test.txt"; int main() { // 初始化DBus连接 DBusError error; dbus_error_init(&error); DBusConnection* dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) { cerr << "DBus Connection Error: " << error.message << endl; dbus_error_free(&error); return 1; } // 创建DBus消息 DBusMessage* dbus_msg = dbus_message_new_signal(DBUS_MSG_PATH, DBUS_MSG_INTERFACE, DBUS_MSG_NAME); if (!dbus_msg) { cerr << "DBus Message Error: Failed to create dbus message." << endl; return 1; } // 打开文件并获取文件句柄 int fd = open(FILE_PATH, O_RDONLY); if (fd < 0) { cerr << "File Error: Failed to open file." << endl; return 1; } // 将文件句柄添加到DBus消息中 dbus_bool_t dbus_ret; dbus_ret = dbus_message_append_args(dbus_msg, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID); if (!dbus_ret) { cerr << "DBus Message Error: Failed to append file descriptor to dbus message." << endl; return 1; } // 发送DBus消息 dbus_ret = dbus_connection_send(dbus_conn, dbus_msg, NULL); if (!dbus_ret) { cerr << "DBus Connection Error: Failed to send dbus message." << endl; return 1; } // 等待DBus消息发送完成 dbus_connection_flush(dbus_conn); // 关闭文件句柄 close(fd); // 释放DBus消息和DBus连接 dbus_message_unref(dbus_msg); dbus_connection_unref(dbus_conn); return 0; } ``` 在接收方进程中,可以通过DBus回调函数接收DBus消息,并从消息中获取文件句柄。以下是一个简单的DBus回调函数示例: ```cpp #include <iostream> #include <dbus/dbus.h> #include <fcntl.h> #include <unistd.h> using namespace std; // 定义DBus消息的格式 static const char* DBUS_MSG_PATH = "/com/example/Test"; static const char* DBUS_MSG_INTERFACE = "com.example.TestInterface"; static const char* DBUS_MSG_NAME = "TestMessage"; static const char* DBUS_MSG_CONTENT = "This is a test message."; // 定义文件路径 static const char* FILE_PATH = "/tmp/test.txt"; // 定义DBus回调函数 DBusHandlerResult dbus_callback(DBusConnection* dbus_conn, DBusMessage* dbus_msg, void* user_data) { if (!dbus_msg) { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } // 获取DBus消息的类型和名称 const char* dbus_msg_type = dbus_message_type_to_string(dbus_message_get_type(dbus_msg)); const char* dbus_msg_name = dbus_message_get_member(dbus_msg); if (strcmp(dbus_msg_type, "signal") == 0 && strcmp(dbus_msg_name, DBUS_MSG_NAME) == 0) { // 从DBus消息中获取文件句柄 int fd; if (!dbus_message_get_args(dbus_msg, NULL, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID)) { cerr << "DBus Message Error: Failed to get arguments from dbus message." << endl; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } // 读取文件内容 char buffer[1024]; int nread = read(fd, buffer, sizeof(buffer)); if (nread < 0) { cerr << "File Error: Failed to read file." << endl; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } // 输出文件内容 cout << "File Content: " << buffer << endl; // 关闭文件句柄 close(fd); return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } int main() { // 初始化DBus连接 DBusError error; dbus_error_init(&error); DBusConnection* dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) { cerr << "DBus Connection Error: " << error.message << endl; dbus_error_free(&error); return 1; } // 注册DBus回调函数 dbus_bus_add_match(dbus_conn, "type='signal',interface='" DBUS_MSG_INTERFACE "',member='" DBUS_MSG_NAME "'", &error); dbus_connection_add_filter(dbus_conn, dbus_callback, NULL, NULL); // 进入DBus消息循环 while (dbus_connection_read_write_dispatch(dbus_conn, -1)) {} // 释放DBus连接 dbus_connection_unref(dbus_conn); return 0; } ``` 需要注意的是,DBus传递文件句柄需要在DBus连接中启用FD_PASSING选项,并且DBus库的版本需要在1.9.8以上。在C语言中,可以使用dbus_connection_set_max_message_size()函数设置DBus消息的最大长度,以避免DBus消息过长导致传递文件句柄失败。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值