版权声明:本文为本文为博主原创文章,转载请注明出处。若有问题,欢迎指正。博客地址:https://www.cnblogs.com/wsg1100/node
上篇文章介绍了实时端socket建立和配置的流程,本篇文章来看bind操做,实时端与非实时端是如何关联起来的?linux
XDDP通信的底层设备为xnpipe,是linux任务与xenomai任务通信的核心,在linux看来是一个字符设备,xnpipe在xenomai内核初始化过程初始化,并完成linux端xnipipe字符设备注册。数据库
bind的主要操做就是根据socket配置,分配资源,如指定通信过程当中分配释放的内存池(xnheap)、缓冲区大小等,并根据端口号,分配对应的xnpipe设备,并将rtdm_fd与xnipipe设备经过数组关联(用次设备号做为数组下标,端口号即次设备号)。下面来看详细过程。数组
与前面函数同样,用户空间实时任务对socket调用bind()
函数,先进入实时库licobalt,再由实时库libcobalt来发起实时内核系统调用:数据结构
saddr.sipc_family = AF_RTIPC; saddr.sipc_port = XDDP_PORT; ret = bind(s, (struct sockaddr *)&saddr, sizeof(saddr));
/*lib\cobalt\rtdm.c*/ COBALT_IMPL(int, bind, (int fd, const struct sockaddr *my_addr, socklen_t addrlen)) { ..... ret = do_ioctl(fd, _RTIOC_BIND, &args); if (ret != -EBADF && ret != -ENOSYS) return set_errno(ret); return __STD(bind(fd, my_addr, addrlen)); } static int do_ioctl(int fd, unsigned int request, void *arg) { .... ret = XENOMAI_SYSCALL3(sc_cobalt_ioctl, fd, request, arg); .... return ret; }
进入系统调用后执行__xddp_ioctl()
.异步
static int __xddp_ioctl(struct rtdm_fd *fd, unsigned int request, void *arg) { struct rtipc_private *priv = rtdm_fd_to_private(fd); struct sockaddr_ipc saddr, *saddrp = &saddr; struct xddp_socket *sk = priv->state; int ret = 0; switch (request) { ...... COMPAT_CASE(_RTIOC_BIND):/*bind操做*/ ret = rtipc_get_sockaddr(fd, &saddrp, arg); ....... ret = __xddp_bind_socket(priv, saddrp); break; ...... } return ret; }
前面文章看了__xddp_ioctl()
中的COMPAT_CASE(_RTIOC_SETSOCKOPT)
分支,如今来看COMPAT_CASE(_RTIOC_BIND)
,__xddp_bind_socket()
.socket
static int __xddp_bind_socket(struct rtipc_private *priv, struct sockaddr_ipc *sa) { struct xddp_socket *sk = priv->state; struct xnpipe_operations ops; rtdm_lockctx_t s; size_t poolsz; void *poolmem; ...../*参数检查*/ poolsz = sk->poolsz; if (poolsz > 0) { poolsz = xnheap_rounded_size(poolsz);//对齐 poolsz += xnheap_rounded_size(sk->reqbufsz); poolmem = xnheap_vmalloc(poolsz); //ZONE_NORMAL中分配,分配后使用xnhead方式进行管理 ...... ret = xnheap_init(&sk->privpool, poolmem, poolsz);/*初始化内存区*/ ....... sk->bufpool = &sk->privpool; } else sk->bufpool = &cobalt_heap; if (sk->reqbufsz > 0) { sk->buffer = xnheap_alloc(sk->bufpool, sk->reqbufsz);/*从bufpool 分配sk->buffer*/ ...... sk->curbufsz = sk->reqbufsz; } /*__xddp_bind_socket()剩余部分*/ ....... }
该函数中先检查相关参数的合法性,而后配置xddp本地内存池privpool
,上篇文章setsocketopt()
只是设置了内存池的大小poolsz
,可是尚未真正分配内存,如今开始分配内存,先将内存大小向上页对齐(PAGE_SIZE为4K),因为xenomai内存池管理缘故,每一个内存池至少为(2*PAGE_SIZE);而后看看poolsz
是否够分配reqbufsz
,不够的话向reqbufsz
对齐。async
大小肯定后正式调用linux接口分配,从ZONE_NORMAL中分配,分配后调用xnheap_init()
将该内存初始化(具体流程参见文章xenomai内核解析--实时内存管理--xnheap)。而后将bufpool指向该内存池。接着分配数据缓冲区bufpool
,从bufpool
指向的内存池中分配缓冲区内存。ide
上面大部分都是关于缓冲区与内存池的设置,到此尚未看到关于数据真正传输控制的东西,__xddp_bind_socket()
接着要完成bind相关工做:函数
static int __xddp_bind_socket(struct rtipc_private *priv, struct sockaddr_ipc *sa) { struct xnpipe_operations ops; ...... /*接上部分*/ sk->fd = rtdm_private_to_fd(priv); ops.output = &__xddp_output_handler; ops.input = &__xddp_input_handler; ops.alloc_ibuf = &__xddp_alloc_handler; ops.free_ibuf = &__xddp_free_handler; ops.free_obuf = &__xddp_free_handler; ops.release = &__xddp_release_handler; ret = xnpipe_connect(sa->sipc_port, &ops, sk);//将SK与OPS与sipc_port联系起来,绑定端口 ....... sk->minor = ret; sa->sipc_port = ret; sk->name = *sa; /*剩余部分*/ }
先取出rtdm_fd,设置struct xnpipe_operations
,struct xnpipe_operations
中的ops为xddp通信过程当中buf分配释放的函数;
struct xnpipe_operations { void (*output)(struct xnpipe_mh *mh, void *xstate); int (*input)(struct xnpipe_mh *mh, int retval, void *xstate); void *(*alloc_ibuf)(size_t size, void *xstate); void (*free_ibuf)(void *buf, void *xstate); void (*free_obuf)(void *buf, void *xstate); void (*release)(void *xstate); };
谁会用到这些buf?xnpipe,xnpipe管理收发的数据包时须要动态管理buf,在具体通信的时候,咱们要为每个数据包在内核空间临时申请一块内存来存放数据,这块内存的申请释放要足够快,并且不能影响实时性,因此得从xnheap中申请,也就是前面xddp-socket->bufpool
指向的内存池,对每块内存的分配释放就是由这个回调函数来完成。须要注意的是,linux端读写数据的时候也是从xddp-socket->bufpool
中分配释放内存,这会在后面文章中看到;
还有一些场合,执行内核用户线程须要在数据到来或发送的时候添加一些hook,这经过output()/input()来设置monitor函数。
接下来调用xnpipe_connect(sa->sipc_port, &ops, sk)
将xddp_socket与linux端的xnipipe函数关联起来,因为xnpipe不是动态分配的,内核配置时肯定xnpipe的数量,以数组的形式,这样确保了肯定性,linux启动时,xenomai内核初始化过程当中就已将xnpipe初始化。
XNPIPE是xenomai内核提供的通信层,是linux任务与xenomai任务通信的核心。每一个xddp socket对应一个XNPIPE,XNPIPE的个数XNPIPE_NDEVS在内核编译时配置,内核默认配置为32个XNPIPE对象保存在全局数组xnpipe_states[XNPIPE_NDEVS]
中,全局bitmap xnpipe_bitmap
中记录着XNPIPE对象分配状况,xnpipe_states[]
内的xpipe对象在xenomai初始化时初始化,在linux VFS下生成对应的设备节点,后一节说明。
内核xnpipe数量配置menuconfig 项以下:
[*] Xenomai/cobalt --->
Sizes and static limits --->
(32) Number of pipe devices
XNPIPE对象结构struct xnpipe_state
以下。
struct xnpipe_state { struct list_head slink; /* Link on sleep queue */ struct list_head alink; /* Link on async queue */ struct list_head inq; /* in/out是从实时端看的相似USB的端口*/ int nrinq; /*链表节点数,代指消息个数*/ struct list_head outq; /* From kernel to user-space */ int nroutq; struct xnsynch synchbase;/*同步*/ struct xnpipe_operations ops;/*执行一些hook函数,如释放消息节点的内存,有消息时执行monitor函数等*/ void *xstate; /* xddp是指向 xddp_socket */ /* Linux kernel part */ unsigned long status;/*状态标识*/ struct fasync_struct *asyncq; wait_queue_head_t readq; /* linux端读等待队列*/ wait_queue_head_t syncq; /*linux端写同步等待队列*/ int wcount; /* 这个设备节点的进程数量*/ size_t ionrd; /*缓冲包数据长度*/ };
最为linux任务与xenomai任务通信的中间人,struct xnpipe_state成员分为两个部分,首先看xenomai相关成员
nrinq
记录。nroutq
记录。xnsynch
等待资源可用。接着是linux相关成员:
status linux端收发操做状态码,各状态码定义以下
#define XNPIPE_KERN_CONN 0x1 /*内核端(rt)已链接*/ #define XNPIPE_KERN_LCLOSE 0x2 /*内核端(rt)关闭*/ #define XNPIPE_USER_CONN 0x4 /*用户端(nrt)已连接*/ #define XNPIPE_USER_SIGIO 0x8 /*用户(nrt)已设置异步通知*/ #define XNPIPE_USER_WREAD 0x10 /*用户(nrt)端读*/ #define XNPIPE_USER_WREAD_READY 0x20 /*用户端(nrt)读就绪*/ #define XNPIPE_USER_WSYNC 0x40 /*用户端(nrt)写同步*/ #define XNPIPE_USER_WSYNC_READY 0x80 /*rt端已读数据,待完成写同步唤醒nrt*/ #define XNPIPE_USER_LCONN 0x100 /*(nrt)端正在执行链接操做*/
asyncq 异步通知队列用于linux端poll操做。
readq linux端读等待队列,当没有数据时会在该队列上阻塞,知道有数据可读。
syncq linux端写同步队列,对同步发送的数据包,会在该队列上阻塞知道数据包被实时端读取。
wcount 使用同一个xnpipe的linux端进程数。
ionrd 缓冲区数据包长度。
回到__xddp_bind_socket()
接着调用xnpipe_connect()
开始执行bind工做,sa->sipc_port
中保存着咱们要使用的rtipc端口(XNPIPE),若是为-1表示自动分配,自动分配后Linux端可经过上节设置的label来找到该xddp。
int xnpipe_connect(int minor, struct xnpipe_operations *ops, void *xstate) { struct xnpipe_state *state; int need_sched = 0, ret; spl_t s; minor = xnpipe_minor_alloc(minor); ..... state = &xnpipe_states[minor]; xnlock_get_irqsave(&nklock, s); ret = xnpipe_set_ops(state, ops); ..... state->status |= XNPIPE_KERN_CONN; xnsynch_init(&state->synchbase, XNSYNCH_FIFO, NULL); state->xstate = xstate; state->ionrd = 0; if (state->status & XNPIPE_USER_CONN) { if (state->status & XNPIPE_USER_WREAD) { /* * Wake up the regular Linux task waiting for * the kernel side to connect (xnpipe_open). */ state->status |= XNPIPE_USER_WREAD_READY; need_sched = 1; } if (state->asyncq) { /* Schedule asynch sig. */ state->status |= XNPIPE_USER_SIGIO; need_sched = 1; } } if (need_sched) xnpipe_schedule_request(); xnlock_put_irqrestore(&nklock, s); return minor; }
在xnpipe_connect中首先根据传入的sa->sipc_port
,分配对应的XNPIPE设备号minor
。
static inline int xnpipe_minor_alloc(int minor) { ...... if (minor == XNPIPE_MINOR_AUTO)//(-1)表示自动分配端口 minor = find_first_zero_bit(xnpipe_bitmap, XNPIPE_NDEVS); if (minor == XNPIPE_NDEVS || (xnpipe_bitmap[minor / BITS_PER_LONG] & (1UL << (minor % BITS_PER_LONG)))) minor = -EBUSY; else xnpipe_bitmap[minor / BITS_PER_LONG] |= (1UL << (minor % BITS_PER_LONG)); ..... return minor; }
xnpipe_minor_alloc()
就是去xnpipe_bitmap
中查看咱们要bind的rtipc_port
是否已经被使用,指定-1则表示自动分配。获得可用的minor
后,就去xnpipe_states[]
中获得对应的struct xnpipe_state
,配置到xnpipe的ops,初始化xenomai资源同步对象state->synchbase
,设置状态掩码为rt已连接,若是nrt此时也处于open xddp设备状态,唤醒 Linux任务,以等待linux内核端链接。
接着__xddp_bind_socket()
剩余部分,若是咱们设置的是使用label方式,自动分配的端口号,就调用xnregistry_enter
注册一个实时对象xnregistry,以便linux端经过路径/proc/xenomai/registry/rtipc/xddp/%s
来打开通信端点。
将分配的XNPIPE minor与rddm_fd对应关系保存到portmap[]
中;
static int __xddp_bind_socket(struct rtipc_private *priv, struct sockaddr_ipc *sa) { /* Set default destination if unset at binding time.*/ if (sk->peer.sipc_port < 0) sk->peer = *sa; if (poolsz > 0) xnheap_set_name(sk->bufpool, "xddp-pool@%d", sa->sipc_port); if (*sk->label) {/*使用xlabel*/ ret = xnregistry_enter(sk->label, sk, &sk->handle, &__xddp_pnode.node); ....... } cobalt_atomic_enter(s); portmap[sk->minor] = rtdm_private_to_fd(priv); __clear_bit(_XDDP_BINDING, &sk->status); __set_bit(_XDDP_BOUND, &sk->status); if (xnselect_signal(&priv->send_block, POLLOUT)) xnsched_run(); cobalt_atomic_leave(s); return 0; }
到此分配好了一个XNPIPE对象,内核全部数据结构初始化好,实时应用可使用该socket发送接收数据了。
上面仅简单说明了xnpipe_state,没有看xnpipe在linux端注册的具体过程,其实就是注册一个字符设备,xnpipe在linux端的初始化是在xenomai内核初始化过程当中调用xnpipe_mount()
完成初始化。
static int __init xenomai_init(void) { ...... ret = xnpipe_mount(); /*注册进程间通信管道xnpipe*/ ...... }
static struct file_operations xnpipe_fops = { .read = xnpipe_read, .write = xnpipe_write, .poll = xnpipe_poll, .unlocked_ioctl = xnpipe_ioctl, .open = xnpipe_open, .release = xnpipe_release, .fasync = xnpipe_fasync }; int xnpipe_mount(void) { struct xnpipe_state *state; struct device *cldev; int i; for (state = &xnpipe_states[0]; state < &xnpipe_states[XNPIPE_NDEVS]; state++) { state->status = 0; state->asyncq = NULL; INIT_LIST_HEAD(&state->inq); /*初始化数据包链表*/ state->nrinq = 0; INIT_LIST_HEAD(&state->outq);/*初始化数据包链表*/ state->nroutq = 0; } /*建立class*/ xnpipe_class = class_create(THIS_MODULE, "frtpipe"); if (IS_ERR(xnpipe_class)) { printk(XENO_ERR "error creating rtpipe class, err=%ld\n", PTR_ERR(xnpipe_class)); return -EBUSY; } /*建立设备*/ for (i = 0; i < XNPIPE_NDEVS; i++) { /*建立rtp1-rtpn*/ cldev = device_create(xnpipe_class, NULL, MKDEV(XNPIPE_DEV_MAJOR, i), NULL, "rtp%d", i); ....... } /*注册字符设备*/ if (register_chrdev(XNPIPE_DEV_MAJOR, "rtpipe", &xnpipe_fops)) { ...... } /*注册xenomai与linux间异步唤醒虚拟中断*/ xnpipe_wakeup_apc = xnapc_alloc("pipe_wakeup", &xnpipe_wakeup_proc, NULL); return 0; }
xnpipe_mount()中,内核构建的时候咱们在指定了多少个xnipipe就要注册多少个字符设备
将xnpipe_states[]内的xnpipe对象初始化。
建立设备类.
建立设备.
device_create() ->device_create_vargs() ->device_create_groups_vargs() ->dev = kzalloc(sizeof(*dev), GFP_KERNEL); ->retval = device_add(dev);
设备添加过程当中,向用户空间发出uevent(添加对象)事件,用户空间的守护进程systemd-udevd
监听到该事件后,systemd-udevd
在/dev
下生成设备节点/dev/rtpX
.
接着注册字符设备,将file_operation与cdev实列关联,其file_operations
为xnpipe_fops
.linux端最终经过这些接口来操做设备/dev/rtpX
来与xenomai 应用通信。
static struct file_operations xnpipe_fops = { .read = xnpipe_read, .write = xnpipe_write, .poll = xnpipe_poll, .unlocked_ioctl = xnpipe_ioctl, .open = xnpipe_open, .release = xnpipe_release, .fasync = xnpipe_fasync };
int __register_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name, const struct file_operations *fops) { struct char_device_struct *cd; struct cdev *cdev; int err = -ENOMEM; cd = __register_chrdev_region(major, baseminor, count, name); cdev = cdev_alloc(); cdev->owner = fops->owner; cdev->ops = fops; kobject_set_name(&cdev->kobj, "%s", name); err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); cd->cdev = cdev; return major ? 0 : cd->major; }
字符设备在内核设备数据库中由cdev结构体表示,字符设备驱动程序的主要工做就是建立并向内核注册cdev实例。注册的方式是调用 __register_chrdev_region,传入注册字符设备的主次设备号和名称(这里须要注意了,次设备号就是数组下标,也就是咱们bind的端口号),而后分配一个 struct cdev
结构,将 cdev 的 ops 成员变量指向这个模块声明的 file_operations
。而后,cdev_add 会将这个字符设备添加到内核中一个叫做 struct kobj_map *cdev_map
的结构,来统一管理全部字符设备。
其中,MKDEV(cd->major, baseminor)
表示将主设备号和次设备号生成一个 dev_t
的整数,而后将这个整数 dev_t
和 cdev
关联起来。
int cdev_add(struct cdev *p, dev_t dev, unsigned count) { int error; p->dev = dev; p->count = count; error = kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); kobject_get(p->kobj.parent); return 0; }
接着注册一个异步过程调用(Asynchronous Procedure Call)xnpipe_wakeup_apc
,apc基于ipipe虚拟中断。经过APC,Xenomai域中的活动可让在Linux内核从新得到控制后,让要延迟处理的程序尽快的在linux域中调度。
xnpipe_wakeup_apc
是ipipe实现的一种虚拟中断机制,主要用于xenomai内核与linux内核的事件通知,其处理过程和ipipe处理硬件中断一致,因此实时性好。其具体实现会在ipipe系列文章中详细解析,敬请关注本博客。
现简单说明其做用:linux端一个任务\(nrt\)与xenomai实时任务\(rt\)使用xddp进行通信,此时\(nrt\)读阻塞等待数据,当\(rt\)向\(nrt\)发送数据后,xenomai内核就会发送一个xnpipe_wakeup_apc
,因为是基于ipipe虚拟中断实现,至关于给linux发送了一个中断,发送后会将该虚拟中断暂时在linux域挂起,当linux获得运行时才会去处理该虚拟中断的handler,进而知道能够唤醒阻塞的\(nrt\),这个过程当中彻底是在xenomai域完成的,对xenomai实时性没有任何影响。
后续文章将从linux端、实时端的数据收发接口进行解析XDDP的详细通信过程,请关注本博客。