linux kernel 中进程间描述符的传递方法及原理
PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
前置说明
本文作为本人csdn blog的主站的备份。(BlogID=082)
本文发布于 2019-03-18 15:09:28,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=082)
环境说明
无
前言
无
背景
为啥我会去找这方面的资料总结?因为我写了一篇另外的文章:《Android匿名共享内存(Anonymous Shared Memory) --- 瞎折腾记录 (驱动程序篇)》(https://blog.csdn.net/u011728480/article/details/88420467),写着写着到了最后,其技术核心就是描述符的进程间传递,导致了我必须得去了解这方面的大致原理。
本文也是作为那篇文章的后续补充吧。
技术大致原理
这里为啥要用“大致”一词,因为还有另外一种比较特殊的传递描述符的方法,文后将会详细说明。
描述符 定义大致可以描述为:一个进程空间中,用一个正整数来代表一个已经被此进程打开的文件,我们可以根据这个正整数来操作这个打开文件。从这里我们可以知道,这个正整数是属于这个进程的,换句话说:不同的进程打开同一个文件的描述符是可能一样的值。
此外这里有一个关于linux 虚拟文件系统的知识需要我们知道,一个是struct node 一个是struct file。在内核中,维护了一个所有打开文件的struct file表,这个代表着这个文件当前的操作状态等等。这个struct file指向了struct node,struct node 代表的是实际数据在存储介质上的哪个位置。换句简单的话来说就是:A和B两个进程打开了同一个文件,那么内核中就会存在一个struct file的变量指向这个打开的文件,对于AB两个进程来说,都会得到一个值可能不一致的描述符,但是这两个不同的描述符指向了内核中保存的同一个struct file变量。
经过上面的说明,我们可以知道的是,至少传递描述符的其中一种原理就是根据当前进程fd找到内核中的struct file,然后在目标进程中申请一个未使用的描述符,然后把这个申请的描述符和这个struct file 关联起来即可。
基于kernel内核态的描述符传输
在上面的原理分析中,其中根据当前进程的描述符得到当前的已经打开文件的struct file 变量,以及其他操作,这些手段都只能够在内核态实现。所以合理的方案是开发一个linux 驱动(android里面就是通过binder驱动),让这个描述符的传递通过一个驱动来完成。这样就可以利用内核态的相关接口来完成我们的事情。下面通过示例的源码来分析一波:
通过fd获取struct file 变量
这里我在网上查到的有两种方案(肯定还有其他方案,因为工作在内核态):一是通过运行进程的pid和fd。二是通过内核态的文件系统提供的fget(struct file *fget(unsigned int fd))
第一种方案:
进程有一个进程控制块,进程控制块中放着一个当前进程打开的描述符表,这个表指向了具体的struct file。
linux kernel : kernel/pid.c
/*
通过以下接口:用pid得到struct pid
*/
struct pid *find_get_pid(pid_t nr)
{
struct pid *pid;
rcu_read_lock();
pid = get_pid(find_vpid(nr));
rcu_read_unlock();
return pid;
}
/*
通过以下接口,得到进程的控制块:struct task_struct. (PIDTYPE_PID)
*/
struct task_struct *pid_task(struct pid *pid, enum pid_type type)
{
struct task_struct *result = NULL;
if (pid) {
struct hlist_node *first;
first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
lockdep_tasklist_lock_is_held());
if (first)
result = hlist_entry(first, struct task_struct, pids[(type)].node);
}
return result;
}
//struct task_struct的files域指向一个struct files_struct结构体。
//struct files_struct的fdt域指向了一个struct fdtable。
//struct fdtable的fd域指向了一个已经打开的struct file 数组,通过当前描述符来作为索引。
int cur_pid;
int cur_fd;
struct pid * tmp_pid = find_get_pid(cur_pid);
struct task_struct * cur_task = pid_task(tmp_pid , PIDTYPE_PID);
struct files_struct * cur_file_struct = cur_task->files;
struct file * cur_file = cur_file_struct->fdt->fd[cur_fd];
第二种方案:
内核态的文件系统提供了一个更简洁的方案:
linuxkernel: fs/file.c
struct file *fget(unsigned int fd)
{
return __fget(fd, FMODE_PATH);
}
给目标进程获取一个未使用的描述符
linuxkernel:fs/open.c或者fs/file.c(我这里有两个内核,linux kernel和android kernel),位置不一致是kernel版本不一致,了解一下就行了。
int get_unused_fd_flags(unsigned flags)
{
return __alloc_fd(current->files, 0, rlimit(RLIMIT_NOFILE), flags);
}
把我们获取的未使用的描述符和我们获取的struct file 关联起来
linuxkernel:fs/open.c或者fs/file.c(我这里有两个内核,linux kernel和android kernel),位置不一致是kernel版本不一致,了解一下就行了。
void fastcall fd_install(unsigned int fd, struct file * file)
{
struct files_struct *files = current->files;
struct fdtable *fdt;
spin_lock(&files->file_lock);
fdt = files_fdtable(files);
BUG_ON(fdt->fd[fd] != NULL);
rcu_assign_pointer(fdt->fd[fd], file);
spin_unlock(&files->file_lock);
}
基于用户态的描述符传输
这个是在AUPE中发现的。貌似以前设计内核的人就预留了这个功能,利用unix的本地socket的一个特殊功能即可。这里用到的调用是以下两个:
就是建立一个本地socket(AF_LOCAL,AF_UNIX),然后通过以下两个接口的特殊功能即可完成描述符的特殊转换。这里我不做过多介绍,有兴趣的看文后的实例。
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
实例分析
用户态实例
userspace_way1_send.c
#include <stdio.h>
#include <sys/un.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char * argv[]){
struct sockaddr_un addr, addr1;
int fd;
if ( 0 > (fd = socket(AF_LOCAL, SOCK_STREAM, 0)) ){
perror("socket()");
return -1;
}
unlink("tmp.tmp");
bzero(&addr, sizeof(struct sockaddr_un));
bzero(&addr1, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, "tmp.tmp", sizeof(addr.sun_path)-1);
socklen_t addr_len = sizeof(struct sockaddr_un);
int ret;
if ( 0 > (ret = bind(fd, (struct sockaddr *) & addr, addr_len ))){
perror("bind()");
return -1;
}
if ( 0 > (ret = listen(fd, 3)) ){
perror("listen()");
return -1;
}
socklen_t addr1_len = sizeof(struct sockaddr_un);
int ret_fd;
if ( 0 > ( ret_fd = accept(fd, (struct sockaddr *)&addr1, &addr1_len))){
perror("accept()");
return -1;
}
int open_file_fd;
if ( 0 > (open_file_fd = open("test.txt", O_RDWR))){
perror("open()");
return -1;
}
char file_content [100] = {0x00};
int read_bytes_num = read(open_file_fd, file_content, 99);
printf("read file content: %s\n", file_content);
char * flags = "@!@";
struct iovec iov = {
.iov_base = flags,
.iov_len = 3
};
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
}control_un;
struct msghdr msg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_flags = 0,
.msg_control = control_un.control,
.msg_controllen = sizeof(control_un.control)
};
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
*((int*)CMSG_DATA(cmsg)) = open_file_fd;
if ( 0 > (ret = sendmsg(ret_fd, &msg, 0))){
perror("sendmsg()");
return -1;
}
close(open_file_fd);
close(fd);
return 0;
}
userspace_way1_recv.c
#include <stdio.h>
#include <sys/un.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char * argv[]){
struct sockaddr_un addr, addr1;
bzero(&addr, sizeof(struct sockaddr_un));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, "tmp.tmp", sizeof(addr.sun_path)-1);
socklen_t addr_len = sizeof(struct sockaddr_un);
int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if ( fd < 0 ) {
perror("socket() error");
return -1;
}
int con_ret = connect(fd, (struct sockaddr *)&addr, addr_len);
if ( con_ret < 0 ) {
perror("connect() error");
return -1;
}
//char * flags = "@!@";
char flags[3] ;
struct iovec iov = {
.iov_base = flags,
.iov_len = 3
};
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
}control_un;
struct msghdr msg = {
.msg_name = NULL,
.msg_namelen = 0,
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_flags = 0,
.msg_control = control_un.control,
.msg_controllen = sizeof(control_un.control)
};
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
*((int*)CMSG_DATA(cmsg)) = -1;
int ret_recv;
if ( 0 > (ret_recv = recvmsg(fd, &msg, 0) )){
perror("recvmsg()");
return -1;
}
int open_file_fd = *((int*)CMSG_DATA(cmsg));
int ret = lseek(open_file_fd, 0, SEEK_SET);
if ( 0 > ret ){
perror("lseek()");
}
char file_content [100] = {0x00};
int read_bytes_num;
if ( 0 > (read_bytes_num = read(open_file_fd, file_content, 99) )){
perror("read()");
return -1;
}
else if ( 0 == read_bytes_num ){
printf("read EOF\n");
}
printf("read file content: %s, fd is %d \n", file_content, open_file_fd);
close(open_file_fd);
close(fd);
return 0;
}
运行截图:(send_way1 发送,recv_way1接收,注意文件指针的重置,否则接收者打印的内容为空)

内核态实例
建立一个trans_fd_drv驱动,然后操作驱动完成不同进程间描述符的转换。
测试环境:
- Ubuntu 18.04
- Linux 4.15.0-46-generic #49-Ubuntu SMP Wed Feb 6 09:33:07 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
驱动源码
transmisson_fd_driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h> //struct cdev
#include <linux/fs.h>//struct file_operations
#include <linux/errno.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/file.h>
static struct class * ptr_cur_class;
MODULE_LICENSE("GPL");
static struct cdev _cur_cdev;
static dev_t _cur_dev;
struct file * ptr_trans_file = NULL;
static int drv_open(struct inode *inode, struct file *filp){
return 0;
}
static int drv_release(struct inode *inode, struct file *filp){
return 0;
}
static long drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
switch(cmd){
case 0:{//get data
int get_fd = arg;
ptr_trans_file = fget(get_fd);
break;
}
case 1:{//set data
int unused_fd = get_unused_fd_flags(0);
fd_install(unused_fd, ptr_trans_file);
__put_user(unused_fd, (int __user *)arg);
break;
}
default:
printk(KERN_ERR "drv_ioctl cmd error ......");
break;
}
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = drv_open,
.release = drv_release,
.unlocked_ioctl = drv_ioctl
};
static int __init trans_fd_drv_init(void)
{
printk(KERN_ERR "trans_fd_drv initing ......");
//void cdev_init(struct cdev *p, const struct file_operations *p);
cdev_init(&_cur_cdev, &fops);
//int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
int ret = alloc_chrdev_region((dev_t *)&_cur_dev, 0, 1, "trans_fd_drv");
if ( ret < 0 ){
printk(KERN_ERR "alloc_chrdev_region error");
return -1;
}
//int cdev_add(struct cdev *p, dev_t dev, unsigned count);
dev_t major = MAJOR(_cur_dev);
ret = cdev_add(&_cur_cdev, _cur_dev, 1);
if ( ret < 0 ){
printk(KERN_ERR "cdev_add error");
return -1;
}
ptr_cur_class = class_create(THIS_MODULE, "trans_fd_drv");
device_create(ptr_cur_class, NULL, _cur_dev, NULL, "trans_fd_drv");
return 0;
}
static void __exit trans_fd_drv_exit(void)
{
//void device_destroy(struct class *cls, dev_t devt);
device_destroy(ptr_cur_class, _cur_dev);
//void class_destroy(struct class *cls);
class_destroy(ptr_cur_class);
//get command and pid
printk(KERN_ERR "trans_fd_drv exiting ......");
//void unregister_chrdev_region(dev_t from, unsigned count);
unregister_chrdev_region(_cur_dev, 1);
//void cdev_del(struct cdev *p);
cdev_del(&_cur_cdev);
}
module_init(trans_fd_drv_init);
module_exit(trans_fd_drv_exit);
操作驱动源码
send_fd_from_drv.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char * argv[]){
int fd;
if ( 0 > (fd = open("/dev/trans_fd_drv", O_RDWR)) ){
perror("open()");
return -1;
}
int show_file_fd;
if ( 0 > (show_file_fd = open("test.txt", O_RDWR)) ){
perror("open()");
return -1;
}
char file_content [100] = {0x00};
int read_bytes_num = read(show_file_fd, file_content, 99);
printf("read file content: %s\n", file_content);
int ret = 0;
if ( 0 > (ret = ioctl(fd, 0, show_file_fd)) ){
perror("ioctl()");
return -1;
}
close(fd);
close(show_file_fd);
return 0;
}
recv_fd_from_drv.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char * argv[]){
int fd;
if ( 0 > (fd = open("/dev/trans_fd_drv", O_RDWR)) ){
perror("open()");
return -1;
}
int show_file_fd;
int ret = 0;
if ( 0 > (ret = ioctl(fd, 1, &show_file_fd)) ){
perror("ioctl()");
return -1;
}
int ret = lseek(show_file_fd, 0, SEEK_SET);
if ( 0 > ret ){
perror("lseek()");
return -1;
}
char file_content [100] = {0x00};
int read_bytes_num = read(show_file_fd, file_content, 99);
printf("read file content: %s\n", file_content);
close(fd);
close(show_file_fd);
return 0;
}
测试截图


后记
总结
- 使自己对用户态和内核态的理解更加的深刻。
- 通过实现一个驱动的方式来直接操作内核态内容,这是一个不错的idea。
- 我发现,很多时候只有亲自动手来试试,才能实际体会到成就感。
- 不折腾,怎么能够进步。
注意:这里关于描述符的转换可以是任意描述符,不一定要是文件描述符(网络描述符等等都可以转换,因为linux的设计原则是:一切皆是文件。有vfs的存在,很多操作都被接口化了。)。
如果有需求:本文所有测试代码可在这里下载(不建议下载,除了makefile我都把代码全部贴出来了的,csdn的积分我没有找到怎么编辑,默认要5分):https://download.csdn.net/download/u011728480/11033121
参考文献
- 无

PS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)