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: 有问题请留言,看到后我会第一时间回复。
linux kernel 中进程间描述符的传递方法及原理的更多相关文章
- linux内核中的文件描述符(二)--socket和文件描述符
http://blog.csdn.net/ce123_zhouwei/article/details/8459730 Linux内核中的文件描述符(二)--socket和文件描述符 Kernel ve ...
- [转] linux系统文件流、文件描述符与进程间关系详解
http://blog.sina.com.cn/s/blog_67b74aea01018ycx.html linux(unix)进程与文件的关系错综复杂,本教程试图详细的阐述这个问题. 包括: ...
- Linux中的文件描述符与打开文件之间的关系
Linux中的文件描述符与打开文件之间的关系 导读 内核(kernel)利用文件描述符(file descriptor)来访问文件.文件描述符是非负整数.打开现存文件或新建文件时,内核会返回一个文件描 ...
- Linux中的文件描述符与打开文件之间的关系------------每天进步一点点系列
http://blog.csdn.net/cywosp/article/details/38965239 1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件. ...
- (转)Linux中的文件描述符
本文转自:http://blog.csdn.net/cywosp/article/details/38965239 作者:cywosp 1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为 ...
- [svc]linux中的文件描述符(file descriptor)和文件
linux中的文件描述符(file descriptor)和文件 linux为了实现一切皆文件的设计哲学,不仅将数据抽象成了文件,也将一切操作和资源抽象成了文件,比如说硬件设备,socket,磁盘,进 ...
- (转)Linux中的文件描述符与打开文件之间的关系
转:http://blog.csdn.net/cywosp/article/details/38965239 1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文 ...
- Linux中断技术、门描述符、IDT(中断描述符表)、异常控制技术总结归类
相关学习资料 <深入理解计算机系统(原书第2版)>.pdf http://zh.wikipedia.org/zh/%E4%B8%AD%E6%96%B7 独辟蹊径品内核:Linux内核源代码 ...
- ZT 父子进程共享文件描述符
转贴自倒霉熊的博客 [linux学习笔记-2]父子进程共享文件描述符 (2009-03-02 23:03:17) 转载▼ 标签: 学习 linux 子进程 文件描述符 杂谈 分类: 学习 #inclu ...
- Linux kernel中常见的宏整理
0x00 宏的基本知识 // object-like #define 宏名 替换列表 换行符 //function-like #define 宏名 ([标识符列表]) 替换列表 换行符 替换列表和标识 ...
随机推荐
- CF1916E Happy Life in University 题解
题目: CF1916E Happy Life in University 链接: 洛谷 或者 CF 前置知识点: 线段树与HH的项链 先简单回顾下HH的项链这题怎么做的吧.先去掉莫队算法,因为这个不是 ...
- Java21 + SpringBoot3集成七牛云对象存储OSS,实现文件上传
目录 前言 实现步骤 引入maven依赖 修改配置文件 创建七牛云配置类 创建文件操作服务类 创建文件操作控制器 前端实现 运行效果 总结 前言 近日心血来潮想做一个开源项目,目标是做一款可以适配多端 ...
- 吉特日化MES & WMS 与周边系统集成架构
作者:情缘 出处:http://www.cnblogs.com/qingyuan/ 关于作者:从事仓库,生产软件方面的开发,在项目管理以及企业经营方面寻求发展之路 版权声明:本文版权归作者和博客园 ...
- 《Boosting Document-Level Relation Extraction by Mining and Injecting Logical Rules》论文阅读笔记
代码 原文地址 摘要 文档级关系抽取(DocRE)旨在从文档中抽取出所有实体对的关系.DocRE 面临的一个主要难题是实体对关系之间的复杂依赖性.与大部分隐式地学习强大表示的现有方法不同,最新的 Lo ...
- 基于OpenTelemetry实现Java微服务调用链跟踪
本文分享自华为云社区<基于OpenTelemetry实现Java微服务调用链跟踪>,作者: 可以交个朋友. 一 背景 随着业务的发展,所有的系统都会走向微服务化体系,微服务进行拆分后,服务 ...
- thinkphp集成editormd一系列实战
介绍 最近php搞了个博客,需要集成markdown编辑器(富文本的太low了,效率也低),用的是时下比较火的editormd,除了基本的文档编辑我这里还实现了几个自己的需求: 使用ctrl-v实现将 ...
- 图文并茂之AES加密
本文改编自:http://www.sohu.com/a/198681357_505794 假设有一个发送方在向接收方发送消息.如果没有任何加密算法,接收方发送的是一个明文消息:"我是小灰&q ...
- form表单如何实现ajax提交
最近在开发一个游戏网关的后台管理系统,总结了下中间碰到的一些问题. 之一就是:form表单如何实现ajax提交? 问题:在使用form表单的时候,一旦点击提交触发submit事件,一般会使得页面跳转, ...
- SpringCloud SpringBoot 组件使用:使用Nacos作为服务的注册中心和配置中心
基础篇 一.什么是Nacos? 官方介绍是这样的: Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您实现动态服务发现.服务配置管理.服务及流量管理. Na ...
- 【libGDX】使用Mesh绘制三角形
1 Mesh 和 ShaderProgram 简介 1.1 创建 Mesh 1)Mesh 的构造方法 public Mesh(boolean isStatic, int maxVertices, ...