在Linux中,由于没有对应的物理设备,RPC管道文件系统是作为一种虚拟的文件系统存在的,其在系统中的注册和注销等操作都必须遵循Linux文件系统规范。关于RPC管道的实现主要分布在rpc_pipe.c文件中。下面是RPC管道文件系统在sunrpc_syms.c文件中的初始化过程:
代码1-1 RPC管道文件系统的初始化过程
static int __init init_sunrpc(void) //__init表示该函数只在初始化过程中调用
{
int err=register_rpc_pipefs(); //注册RPC管道文件系统"rpc_pipefs"
if (err)
goto out;
err=rpc_init_mempool(); //初始化内存池
if (err) {
unregister_rpc_pipefs(); //注销RPC管道文件系统"rpc_pipefs"
goto out;
}
#ifdef RPC_DEBUG
rpc_register_sysctl(); //注册SYSCTL,方便用户读取或调整系统设置
#endif
#ifdef CONFIG_PROC_FS
rpc_proc_init(); //初始化PROC,方便用户获取运行时信息
#endif
cache_register(&ip_map_cache); //注册缓冲
cache_register(&unix_gid_cache);
svc_init_xprt_sock();
init_socket_xprt(); //初始化套接字
rpcauth_init_module(); //初始化RPC鉴权模块
out:
return err;
}
和其他文件系统一样,一个RPC管道是作为RPC管道文件系统下的一个文件存在的,下面是RPC管道的接口定义:
static const struct file_operations rpc_pipe_fops={
.owner=THIS_MODULE,
.llseek=no_llseek,
.read=rpc_pipe_read, //读取管道数据
.write=rpc_pipe_write, //写入管道数据
.poll=rpc_pipe_poll, //轮询管道
.ioctl=rpc_pipe_ioctl, //管道的ioctl
.open=rpc pipe_open, //打开管道
.release=rpc_pipe_release, //释放管道
};
一个RPC管道就是RPC管道文件系统的一个节点,下面是RPC节点的定义:
struct rpc_inode {
struct inode vfs_inode; //继承VFS节点
void *private;
struct list_head pipe;
struct list_head in_upcall; //通知上层节点
struct list_head in_downcall; //通知下层节点
int pipelen;
int nreaders; //读数据线程数
int nwriters; //写数据线程数
int nkern_readwriters;
wait_queue_head_t waitq; //等待队列
#define RPC_PIPE_WAIT_FOR_OPEN 1
int flags;
struct rpc_pipe_ops *ops; //管道选项
struct delayed_work queue_timeout;
};
struct rpc_pipe_ops {
ssize_t (*upcall)(struct file *, struct rpc_pipe_msg *, char __user *, size_t);
ssize_t (*downcall)(struct file *, const char __user *, size_t);
void (*release_pipe)(struct inode *); //释放管道
int (*open_pipe)(struct inode *); //打开管道
void (*destroy_msg)(struct rpc_pipe_msg *); //销毁管道消息
};
为了利用RPC在Linux内核和用户空间之间进行通信,需要创建一个RPC管道,下面是创建一个RPC管道的实现过程:
代码1-2 创建RPC管道的过程
struct dentry * rpc_mkpipe(struct dentry *parent, const char *name, void *private, struct rpc_pipe_ops *ops, int flags)
{
struct dentry *dentry;
struct inode *dir, *inode;
struct rpc_inode *rpci;
dentry=rpc_lookup_create(parent, name, strlen(name), 0); //创建目录
if (IS_ERR(dentry))
return dentry;
dir=parent->d_inode;
if (dentry->d_inode) {
rpci=RPC_I(dentry->d_inode);
if (rpci->private !=private ||
rpci->ops !=ops ||
rpci->flags !=flags) {
dput (dentry);
dentry=ERR_PTR(-EBUSY);
}
rpci->nkern_readwriters++;
goto out;
}
inode=rpc_get_inode(dir->i_sb, S_IFIFO | S_IRUSR | S_IWUSR); //配置权限
if (!inode)
goto err_dput;
inode->i_ino=iunique(dir->i_sb, 100);
inode->i_fop=&rpc_pipe_fops;
d_instantiate(dentry, inode);
rpci=RPC_I(inode);
rpci->private=private;
rpci->flags=flags;
rpci->ops=ops;
rpci->nkern_readwriters=1;
fsnotify_create(dir, dentry);
dget(dentry);
out:
mutex_unlock(&dir->i_mutex);
return dentry;
err_dput:
dput(dentry);
dentry=ERR_PTR(-ENOMEM);
printk(KERN_WARNING "%s: %s() failed to create pipe %s/%s (errno=%d)\n",__FILE__, __func__, parent->d_name.name, name,-ENOMEM);
goto out;
}
通过RPC管道文件系统,调用者可以像操作其他文件那样进行RPC管道的操作,常见的操作有读取数据、写入数据、查询管道信息、创建路径、删除路径、创建RPC管道等。
需要说明的是,当管道数据可读时,需要调用rpc_queue_upcall()函数通知用户空间,当管道可写入数据时,则不需要通知用户空间。在通知用户空间RPC管道的状态时,需要将消息封装在rpc_pipe_msg结构体中。下面是rpc_queue_upcall()函数的实现过程:
代码1-3 RPC通知用户空间的过程
int rpc_queue_upcall(struct inode *inode, struct rpc_pipe_msg *msg)
{
struct rpc_inode *rpci=RPC_I(inode);
int res=-EPIPE;
spin_lock(&inode->i_lock); //自旋锁
if (rpci->ops==NULL)
goto out;
if (rpci->nreaders) {
list_add_tail(&msg->list, &rpci->pipe); //添加到消息队列
rpci->pipelen+=msg->len;
res=0;
} else if (rpci->flags & RPC_PIPE_WAIT_FOR_OPEN) { //如果RPC管道等待打开
if (list_empty(&rpci->pipe))
queue_delayed_work(rpciod_workqueue, //添加到rpciod_workqueue
&rpci->queue_timeout,
RPC_UPCALL_TIMEOUT);
list_add_tail(&msg->list, &rpci->pipe); //添加到消息队列
rpci->pipelen += msg->len;
res=0;
}
out:
spin_unlock(&inode->i_lock);
wake_up(&rpci->waitq); //唤醒等待队列
return res;
}
在auth_gss.c文件中,有RPC管道的运用实例,感兴趣的朋友可以自行研读。