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;
}

  测试截图

后记


  总结

  1. 使自己对用户态和内核态的理解更加的深刻。
  2. 通过实现一个驱动的方式来直接操作内核态内容,这是一个不错的idea。
  3. 我发现,很多时候只有亲自动手来试试,才能实际体会到成就感。
  4. 不折腾,怎么能够进步。

  注意:这里关于描述符的转换可以是任意描述符,不一定要是文件描述符(网络描述符等等都可以转换,因为linux的设计原则是:一切皆是文件。有vfs的存在,很多操作都被接口化了。)。

  如果有需求:本文所有测试代码可在这里下载(不建议下载,除了makefile我都把代码全部贴出来了的,csdn的积分我没有找到怎么编辑,默认要5分):https://download.csdn.net/download/u011728480/11033121

参考文献


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

linux kernel 中进程间描述符的传递方法及原理的更多相关文章

  1. linux内核中的文件描述符(二)--socket和文件描述符

    http://blog.csdn.net/ce123_zhouwei/article/details/8459730 Linux内核中的文件描述符(二)--socket和文件描述符 Kernel ve ...

  2. [转] linux系统文件流、文件描述符与进程间关系详解

    http://blog.sina.com.cn/s/blog_67b74aea01018ycx.html linux(unix)进程与文件的关系错综复杂,本教程试图详细的阐述这个问题. 包括:     ...

  3. Linux中的文件描述符与打开文件之间的关系

    Linux中的文件描述符与打开文件之间的关系 导读 内核(kernel)利用文件描述符(file descriptor)来访问文件.文件描述符是非负整数.打开现存文件或新建文件时,内核会返回一个文件描 ...

  4. Linux中的文件描述符与打开文件之间的关系------------每天进步一点点系列

    http://blog.csdn.net/cywosp/article/details/38965239 1. 概述     在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件. ...

  5. (转)Linux中的文件描述符

    本文转自:http://blog.csdn.net/cywosp/article/details/38965239 作者:cywosp 1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为 ...

  6. [svc]linux中的文件描述符(file descriptor)和文件

    linux中的文件描述符(file descriptor)和文件 linux为了实现一切皆文件的设计哲学,不仅将数据抽象成了文件,也将一切操作和资源抽象成了文件,比如说硬件设备,socket,磁盘,进 ...

  7. (转)Linux中的文件描述符与打开文件之间的关系

    转:http://blog.csdn.net/cywosp/article/details/38965239 1. 概述     在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文 ...

  8. Linux中断技术、门描述符、IDT(中断描述符表)、异常控制技术总结归类

    相关学习资料 <深入理解计算机系统(原书第2版)>.pdf http://zh.wikipedia.org/zh/%E4%B8%AD%E6%96%B7 独辟蹊径品内核:Linux内核源代码 ...

  9. ZT 父子进程共享文件描述符

    转贴自倒霉熊的博客 [linux学习笔记-2]父子进程共享文件描述符 (2009-03-02 23:03:17) 转载▼ 标签: 学习 linux 子进程 文件描述符 杂谈 分类: 学习 #inclu ...

  10. Linux kernel中常见的宏整理

    0x00 宏的基本知识 // object-like #define 宏名 替换列表 换行符 //function-like #define 宏名 ([标识符列表]) 替换列表 换行符 替换列表和标识 ...

随机推荐

  1. KB0001.修改DoraCloud管理系统的IP地址

    KB0001.修改DoraCloud管理系统的IP地址 DoraCloud 管理系统是一个CentOS Linux的虚拟机.我们既可以通过DoraCloud后台管理系统修改它的IP地址,也可以通过Ce ...

  2. Java开发学习(三十八)----SpringBoot整合junit

    先来回顾下 Spring 整合 junit @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = Spring ...

  3. FireDac 连接 SQL SERVER 2014 - LocalDB

    易博龙官方的文档没有更新,官方的文档只能连接local-db2012 微软官方关于local-db 2012的描述 如下: 但是现在我开始使用SQL SERVER LOCAL-DB 2014了,因为今 ...

  4. Linux-crontab的使用

    一.什么是crontab?crontab 是有cron (crond) 这个系统服务来控制的,cron服务是linux的内置服务,类似于window下的计划任务,但它不会开机自动启动 二.如何使用?c ...

  5. [Java]format string is malformed java

    format string is malformed java 最近在做代码审查,发现很多在使用 String.format 的时候遇到了IDEA报的 Format string 'xxx' is m ...

  6. 云计算 - 负载均衡SLB方案全解与实战

    云计算 - 负载均衡SLB方案全解与实战,介绍SLB的核心技术.用户最佳实践.阿里云 SLB产品举例.应用场景. 关注[TechLeadCloud],分享互联网架构.云服务技术的全维度知识.作者拥有1 ...

  7. Ubuntu20.04/22.04 ESP32 命令行开发环境配置

    ESP32 芯片系列 ESP32分三个系列 ESP32-S ESP32-S3: Xtensa 32位 LX7 双核 240 MHz, 384KB ROM, 512KB SRAM, QFN7x7, 56 ...

  8. Java容器及其常用方法汇总

    1 概述 Java Collections 框架中包含了大量的接口及其实现类和操作它们的算法,主要包括列表(List).集合(Set).映射(Map),如下: 接口 实现类 数据结构 初始容量 加载因 ...

  9. JVM详解

    1 JVM运行机制概述 JVM运行机制 类加载机制: 类加载过程由类加载器来完成,即由ClassLoader及其子类实现,有隐式加载和显式加载两种方式.隐式加载是指在使用new等方式创建对象时会隐式调 ...

  10. C++ STL学习

    C++ STL学习 目录 C++ STL学习 容器库概览 对可以保存在容器中的元素的限制 容器支持的操作 所有容器都支持的操作或容器成员 迭代器 迭代器的公共操作 迭代器的类型 迭代器的const属性 ...