版權聲明:本文爲本文爲博主原創文章,轉載請註明出處。若有問題,歡迎指正。博客地址: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的詳細通信過程,請關注本博客。