linux kernel 中进程间描述符的传递方法及原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011728480/article/details/88553602

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

#PS:这个只是 《 我自己 》理解,如果和你的

#原则相冲突,请谅解,勿喷

背景

为啥我会去找这方面的资料总结?因为我写了一篇另外的文章:《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 我发现,很多时候只有亲自动手来试试,才能实际体会到成就感。
3 不折腾,怎么能够进步。

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

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

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

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

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

猜你喜欢

转载自blog.csdn.net/u011728480/article/details/88553602