版權聲明:本文為本文為博主原創文章,轉載請注明出處。如有問題,歡迎指正。博客地址:https://www.cnblogs.com/wsg1100/
1.概述
上篇文章xenomai內核解析--實時IPC概述中介紹了RTIPC,從這篇文章開始開始深入xenomai內核,解析RTIPC的具體實現。
XDDP、IDDP和BUFP由於應用場景不一樣,所以底層不一樣,但也區別不大。XDDP用於xenomai任務與普通Linux任務通訊,提供兩種方式,一種是每次讀寫作為一個數據報來操作,對應實時任務間的通訊方式IDDP;另一種為可以將多次讀寫的數據緩沖最后組成一個大的數據報發送,對應實時任務間的BUFP方式。所以解析了XDDP原理,那么IDDP和BUFP自然也就懂了,后面文章我也會簡單說一下IDDP、XDDP。
需要先說明一下 XDDP幾乎涉及了xenomai的所有關鍵組件,通過解析xenomai內核XDDP的實現源碼,你會明白:
- xenomai內核:
- XDDP的詳細實現。
- 實時設備驅動模型:RTDM是如何管理協議類設備的,應用是如何找到並使用具體的協議設備的(其實和Linux類似),如何為xenomai添加一個自定義協議設備等。
- Linux端:字符類設備管理、虛擬文件系統VFS;
- 通訊過程中的內存分配釋放:實時內存堆-xnheap,詳見xenomai內核解析--實時內存管理--xnheap
- xenomai資源同步互斥機制:xnsynch
- 如何跨域喚醒指定任務:ipipe虛擬中斷xnpipe_wakeup_apc
2.XDDP的使用注意事項
上篇文章已經介紹了具體的使用方法,linux端通過read()、write()
讀寫/dev/rtp<minor>
或/proc/xenomai/registry/rtipc/xddp/label
來通訊,Xenomai端通過套接字recvfrom()或read()
來接收數據,sendto()或write()
來發送數據。其中需要注意的是:
- XDDP 只能由xenomai應用(使用Libcobalt庫編譯)創建.
- 由於端口號與Linux端次設備號綁定,所以必須兩邊都關閉釋放了才能再次使用同一個端口(可見文末總框圖)。
下面我們就沿着這個流程到內核里面一探究竟,看看在內核里面,都創建了哪些數據結構,做了哪些事情。
3.解析socket函數
從調用socket()函數開始。對於xenomai實時程序,該函數不是直接就執行系統調用,而是由xenomai實時庫libcobalt中實現,實時應用編譯時會鏈接到該庫。
/*xenomai-3.x.x\lib\cobalt\rtdm.c*/
COBALT_IMPL(int, socket, (int protocol_family, int socket_type, int protocol))
{
int s;
s = XENOMAI_SYSCALL3(sc_cobalt_socket, protocol_family,
socket_type, protocol);
if (s < 0) {
s = __STD(socket(protocol_family, socket_type, protocol));
}
return s;
}
從該函數可以看到,首先執行xenomai內核調用,如果xenomai系統調用返回負值,一種情況時產生了錯誤,另一種情況說明這些參數不是要實時內核提供服務的,接着才去調用標准庫執行linux的系統調用。這就實現了同一接口也可以讓linux提供服務。
創建socket的時候有三個參數,一個是protocol_family
表示地址族,在linux中,如下兩種是比較熟悉的。
#define AF_UNIX 1/* Unix domain sockets */
#define AF_INET 2/* Internet IP Protocol */
對於xenomai,protocol_family
只有一種,如果自己為xenomai內核添加自定義的協議設備就可以通過該參數識別:
/* Address family */
#define AF_RTIPC 111
/* Protocol family */
#define PF_RTIPC AF_RTIPC
第二個參數是socket_type
,也即 Socket 的類型。類型是比較少的。
第三個參數是protocol
,是協議。協議數目是比較多的,也就是說,多個協議會屬於同一種類 型。
常用的 Socket 類型有三種,分別是 SOCK_STREAM
、SOCK_DGRAM
和 SOCK_RAW
。
enum sock_type {
SOCK_STREAM = 1,
SOCK_DGRAM = 2,
SOCK_RAW = 3,
......
};
SOCK_STREAM
是面向數據流的,協議 IPPROTO_TCP屬於這種類型。SOCK_DGRAM
是面 向數據報的,協議IPPROTO_UDP 屬於這種類型。如果在內核里面看的話,IPPROTO_ICMP 也屬於這種類型。SOCK_RAW
是原始的 IP 包,IPPROTO_IP 屬於這種類型。
對於socket_type
,在xenomai 中,通訊是在系統內部,統一為數據報即SOCK_DGRAM
,其余無效。xenomai提供的protocol
如下幾種:
enum {
/** Default protocol (IDDP) */
IPCPROTO_IPC = 0,
IPCPROTO_XDDP = 1,
IPCPROTO_BUFP = 3,
IPCPROTO_MAX
};
其實xenomai提供的rtipc只作為實時進程間通訊,內部與linux socket一點關系都沒有,從上就可以看出,僅是函數接口相同而已。
這一節,我們重點看IPCPROTO_XDDP
協議。實時系統調用sc_cobalt_socket
對應內核代碼如下。它會調用__rtdm_dev_socket()
/*kernel\xenomai\posix\io.c*/
COBALT_SYSCALL(socket, lostage,
(int protocol_family, int socket_type, int protocol))
{
return __rtdm_dev_socket(protocol_family, socket_type, protocol);
}
/*kernel\xenomai\rtdm\core.c*/
int __rtdm_dev_socket(int protocol_family, int socket_type,
int protocol)
{
struct rtdm_dev_context *context;
struct rtdm_device *dev;
int ufd, ret;
secondary_mode_only();
dev = __rtdm_get_protodev(protocol_family, socket_type);
......
ufd = __rtdm_anon_getfd("[rtdm-socket]", O_RDWR);
......
ret = create_instance(ufd, dev, &context);
......
if (dev->ops.socket) {
ret = dev->ops.socket(&context->fd, protocol);
......
}
......
ret = rtdm_fd_register(&context->fd, ufd);
......
return ufd;
}
secondary_mode_only()
表示目前上下文必須是linux域,應為涉及到linux一些資源分配,如文件描述符。你可能會疑惑,我們創建調用socket函數的應用已經在實時線程里了,而且我們使用實時庫libcobalt發起的系統調用,進內核后應該是haed域,而這里為什么還secondary_mode_only?解答這個問題請移步本博客其他文章xenomai內核解析--雙核系統調用(一)小節的權限控制。
先是根據protocol_family
和socket_type
轉換為xnkey_t
來查找對應的rtdm_device.
struct rtdm_device *__rtdm_get_protodev(int protocol_family, int socket_type)
{
struct rtdm_device *dev = NULL;
struct xnid *xnid;
xnkey_t id;
secondary_mode_only();
id = get_proto_id(protocol_family, socket_type);
mutex_lock(®ister_lock);
xnid = xnid_fetch(&protocol_devices, id);
if (xnid) {
dev = container_of(xnid, struct rtdm_device, proto.id);
__rtdm_get_device(dev);
}
mutex_unlock(®ister_lock);
return dev;
}
3.1 RTDM Protocol Devices
id類型為longlong,高32位為protocol_family
,低32位為socket_type
,將它作為key在紅黑樹protocol_devices
上找到對應的設備。
static struct rb_root protocol_devices;
protocol_devices
是一個全局變量,其類型是struct rb_root,我們知道xenomai 實時驅動模型(RTDM)將所有實時設備分為兩種Protocol Devices(協議類設備)
和CharacterDevices(字符類設備)
,protocol_devices
作為所有Protocol Devices的根節點,所有Protocol Devices驅動注冊時調用rtdm_dev_register()
后都會掛到protocol_devices
上。
xenomai中實時進程間通訊RTDM設備rtipc及其rtipc_driver,在drivers\xenomai\ipc\rtipc.c
中如下:
static struct rtdm_driver rtipc_driver = {
.profile_info = RTDM_PROFILE_INFO(rtipc,
RTDM_CLASS_RTIPC,
RTDM_SUBCLASS_GENERIC,
1),
.device_flags = RTDM_PROTOCOL_DEVICE,
.device_count = 1,
.context_size = sizeof(struct rtipc_private),
.protocol_family = PF_RTIPC, /*地址族*/
.socket_type = SOCK_DGRAM, /*socket類型*/
.ops = {
.socket = rtipc_socket,
.close = rtipc_close,
.recvmsg_rt = rtipc_recvmsg,
.recvmsg_nrt = NULL,
.sendmsg_rt = rtipc_sendmsg,
.sendmsg_nrt = NULL,
.ioctl_rt = rtipc_ioctl,
.ioctl_nrt = rtipc_ioctl,
.read_rt = rtipc_read,
.read_nrt = NULL,
.write_rt = rtipc_write,
.write_nrt = NULL,
.select = rtipc_select,
},
};
static struct rtdm_device device = {
.driver = &rtipc_driver,
.label = "rtipc",
};
從rtipc_driver
中的rtdm_fd_ops
我們就可以看出一二,創建一個rtipc socket后,對該socket的數據收發、讀寫等操作都會調用到rtdm_fd_ops
內的rtipc_sendmsg()、rtipc_recvmsg()
等函數。
同樣,如果需要自定義一個xenomai Protocol Devices,實現這些函數,為該設備設置好protocol_family
和socket_type
后,我們的實時應用就可以通過調用socket(),然后xenomai RTDM通過(protocol_family<<32) | socket_type
作為xnkey_t到該設備及對應的driver,來讓該設備為我們服務。
回到__rtdm_dev_socket()
,接下來調用__rtdm_anon_getfd
完成在用戶空間定義一個[rtdm-socket]
的文件,將[rtdm-socket]
與rtdm_dumb_fops
結合起來。
int __rtdm_dev_socket(int protocol_family, int socket_type,
int protocol)
{
......
ufd = __rtdm_anon_getfd("[rtdm-socket]", O_RDWR);
......
......
ret = create_instance(ufd, dev, &context);
......
}
為什么要這樣做呢?用戶空間需要一個文件描述符來與內核rtdm_fd對應起來,ufd作為用戶套接字socket,后面的代碼會看到ufd成為紅黑樹上查找rtdm_fd的keyt_t,當使用socket接口對ufd操作時,到了內核里就會用ufd找到對應的rtdm_fd。但是直接對ufd使用read/write等操作是不允許的,所以還需要為ufd設置file_operation rtdm_dumb_fops
,rtdm_dumb_fops
里的函數均打印一條警告信息,直接對ufd使用read/write等操作時就內核就會輸出WARNING信息。
static inline void warn_user(struct file *file, const char *call)
{
struct dentry *dentry = file->f_path.dentry;
printk(XENO_WARNING
"%s[%d] called regular %s() on /dev/rtdm/%s\n",
current->comm, task_pid_nr(current), call + 5, dentry->d_name.name);
}
static ssize_t dumb_read(struct file *file, char __user *buf,
size_t count, loff_t __user *ppos)
{
warn_user(file, __func__);
return -EINVAL;
}
.....
const struct file_operations rtdm_dumb_fops = {
.read = dumb_read,
.write = dumb_write,
.poll = dumb_poll,
.unlocked_ioctl = dumb_ioctl,
};
接着調用create_instance()
創建rtdm_dev_context
並初始化對應的結構體,在RTDM中,struct rtdm_driver與struct rtdm_device描述了一個設備的共有抽象信息,但具體的設備有其操作的具體數據,稱為實時設備的上下文rtdm_dev_context
,結構如下:
struct rtdm_dev_context {
struct rtdm_fd fd;
/** Set of active device operation handlers */
/** Reference to owning device */
struct rtdm_device *device;
/** Begin of driver defined context data structure */
char dev_private[0];
};
其中成員fd
類型為struct rtdm_fd
,其中記錄着該設備的OPS,所屬線程等信息。
成員變量dev_private為私有數據的起始地址,至於設備的私有數據有多大,在rtdm_device用context_size
表,對於rtipc,其大小為sizeof(struct rtipc_private)
,所以為rtipc創建rtdm_dev_context時分配的內存大小為sizeof(struct rtdm_dev_context) + rtipc_driver->context_size
。
struct rtdm_fd
如下
struct rtdm_fd {
unsigned int magic; /*RTDM_FD_MAGIC*/
struct rtdm_fd_ops *ops; /*RTDM設備可用的操作,*/
struct cobalt_ppd *owner; /*所屬Process*/
unsigned int refs; /*打開計數*/
int minor;
int oflags;
#ifdef CONFIG_XENO_ARCH_SYS3264
int compat;
#endif
struct list_head cleanup;
};
- magic fd的類型 RTDM_FD_MAGIC
- *ops 描述RTDM設備可用的操作。 這些處理程序由RTDM設備驅動程序(rtdm_driver)實現。
- *owner該rtdm_fd所屬的應用程序。
- refs 記錄該fd的引用次數,當為0時會觸發執行
ops->close()
- minor
- oflags
- cleanup
create_instance()
執行完后各結構暫時如下:
接着執行ops.socket()
也就是rtipc_socket()
,傳入參數為rtdm_fd
和protocol(IPCPROTO_XDDP)
.
if (dev->ops.socket) {
ret = dev->ops.socket(&context->fd, protocol);
......
}
static int rtipc_socket(struct rtdm_fd *fd, int protocol)
{
struct rtipc_protocol *proto;
struct rtipc_private *priv;
int ret;
if (protocol < 0 || protocol >= IPCPROTO_MAX)
return -EPROTONOSUPPORT;
if (protocol == IPCPROTO_IPC)
/* Default protocol is IDDP */
protocol = IPCPROTO_IDDP;
proto = protocols[protocol - 1];
if (proto == NULL) /* Not compiled in? */
return -ENOPROTOOPT;
priv = rtdm_fd_to_private(fd);
priv->proto = proto;
priv->state = kmalloc(proto->proto_statesz, GFP_KERNEL);
......
xnselect_init(&priv->send_block);
xnselect_init(&priv->recv_block);
ret = proto->proto_ops.socket(fd);
......
return ret;
}
先看協議是不是xenomai支持的,如果協議類型為IPCPROTO_IPC
,那就是默認協議IPCPROTO_IDDP
,接着從數組中取出協議對應的rtipc_protocol* proto
,之前說過rtipc提供三種進程間通訊:IDDP、XDDP、BUFP,用結構體struct rtipc_protocol
來描述它們,保存在數組rtipc_protocol
中:
static struct rtipc_protocol *protocols[IPCPROTO_MAX] = {
#ifdef CONFIG_XENO_DRIVERS_RTIPC_XDDP
[IPCPROTO_XDDP - 1] = &xddp_proto_driver,
#endif
#ifdef CONFIG_XENO_DRIVERS_RTIPC_IDDP
[IPCPROTO_IDDP - 1] = &iddp_proto_driver,
#endif
#ifdef CONFIG_XENO_DRIVERS_RTIPC_BUFP
[IPCPROTO_BUFP - 1] = &bufp_proto_driver,
#endif
};
接着根據rtdm_fd得到rtdm_dev_context
內的dev_private[0]
,這里先看一下struct rtipc_private
各成員變量的意思:
struct rtipc_private {
struct rtipc_protocol *proto;
DECLARE_XNSELECT(send_block);//struct xnselect send_block
DECLARE_XNSELECT(recv_block);//struct xnselect recv_block
void *state;
};
- proto指向具體的rtipc_protocol
- send_block、send_block是鏈表,發送或接收阻塞時會插入該鏈表
- state 與
dev_private[0]
類似,指向不同協議所需的而外空間。對於XDDP說指向sizeof(struct xddp_socket)
大小內存。
得到dev_private[0]后,強制類型轉換為structr tipc_private *priv
后開始初始化結構體tipc_private
內各成員.最后調用具體協議的下的socket()
,傳入參數fd,對應XDDP協議xddp_socket()
;
到此知道,實時應用對socket描述符的操作最后都是由實時設備驅動中具體函數來完成,后續的配置數據收發等都是按該路徑進行執行。
回到xddp socket():
static int xddp_socket(struct rtdm_fd *fd)
{
struct rtipc_private *priv = rtdm_fd_to_private(fd);
struct xddp_socket *sk = priv->state;
sk->magic = XDDP_SOCKET_MAGIC;
sk->name = nullsa; /* Unbound */
sk->peer = nullsa;
sk->minor = -1;
sk->handle = 0;
*sk->label = 0;
sk->poolsz = 0;
sk->buffer = NULL;
sk->buffer_port = -1;
sk->bufpool = NULL;
sk->fillsz = 0;
sk->status = 0;
sk->timeout = RTDM_TIMEOUT_INFINITE;
sk->curbufsz = 0;
sk->reqbufsz = 0;
sk->monitor = NULL;
rtdm_lock_init(&sk->lock);
sk->priv = priv;
return 0;
}
在xddp_socket()
主要初始化struct xddp_socket
,也很重要,后面會詳細解析它。xddp_socket()
執行完畢后回到__rtdm_dev_socket()
,接下來調用rtdm_fd_register()
將rdtm_fd並注冊到cobalt_ppd
中。
int __rtdm_dev_socket(int protocol_family, int socket_type,
int protocol)
{
......
ret = rtdm_fd_register(&context->fd, ufd);
.....
return ufd;
}
int rtdm_fd_register(struct rtdm_fd *fd, int ufd)
{
struct rtdm_fd_index *idx;
struct cobalt_ppd *ppd;
spl_t s;
int ret = 0;
ppd = cobalt_ppd_get(0);
idx = kmalloc(sizeof(*idx), GFP_KERNEL);
......
idx->fd = fd;
......
ret = xnid_enter(&ppd->fds, &idx->id, ufd);
.....
return ret;
}
3.2 rtdm_fd_index
首先獲取當前進程的struct cobalt_ppd
,然后分配一個struct rtdm_fd_index
,看名字知道rtdm_fd的index結構,怎么索引呢?通過傳入的ufd,傳入的ufd作為key,構造一個rtdm_fd_index
,然后插入ppd->fds
,ppd->fds
時一顆紅黑樹,每個實時任務創建或打開的實時設備fd都是由fds來記錄着。
將ufd與rtdm_fd聯系起來以后,socket函數執行完畢,返回ufd
,用戶空間通過ufd發起內核調用時,就可通過ufd找到內核里相關的所有的結構。
完成各數據結構分配關系鏈接后,下一步就可以對該socket進行配置了。解析setsockopt()
函數之前,上面圖中struct xddp_socket
、struct cobalt_ppd
兩個結構體還有沒有介紹,如下:
3.3 cobalt_ppd介紹
struct cobalt_ppd
(即Cobalt內核調度的實時進程的私有數據 ,Cobalt_process Private data),結構如下:
struct cobalt_ppd {
struct cobalt_umm umm;
unsigned long mayday_tramp;
atomic_t refcnt;
char *exe_path;
struct rb_root fds;
};
-
umm
該進程內管理的一片內存池,當實時任務內核上下文需要分配內存時,就會從該區域中獲取。在xenomai中為避免向linux分配內存影響實時性,xenomai采取的方式是,先向linux分配所需的一片內存,然后再由自己管理該內存的分配釋放,管理該內存池的分配釋放算法是實時可預測的,從而達到不影響實時性的目的。當實時任務內核上下文需要分配內存時,就會從該區域中獲取。關於實時內存堆的管理,可查看本博客其他文章.
struct cobalt_umm {
struct xnheap heap;/*內存池*
atomic_t refcount; /*refcount是該片內存的使用計數*/
void (*release)(struct cobalt_umm *umm);/*release釋放函數*/
};
-
refcnt
cobalt_ppd引用計數,釋放的時候使用. -
fds
是一棵紅黑樹,保存着該進程打開的實時驅動設備的文件描述符rtdm_fd,可以類比Linux中進程打開的文件描述符集,rtdm_fd結構上面說到過.
xenomai內核中另外兩個個heap需要區分一下:
cobalt_kernel_ppd:Cobalt_process.cobalt_ppd.cobalt_umm
內的heap是每個Cobalt進程私有的,除此之外xenomai內核中還有一個全局的struct cobalt_ppd
—cobalt_kernel_ppd,供cobalt內核/內核線程工作過程中的內存分配。
cobalt_heap:xenomai的系統內存池,XDDP數據緩沖區默認從該區域分配。
cobalt_heap,其大小可編譯時配置或通過傳遞內核參數設置,在xenomai內核初始化時從linux分配內存,然后由xenomai初始化管理。
static int __init xenomai_init(void) { ....... ret = sys_init(); ...... }
3.4 xddp_socket
接着看struct xddp_socket
,是XDDP socket核心,管理着XDDP大部分資源,xddp_socket結構體成員及作用如下:
struct xddp_socket {
int magic;
struct sockaddr_ipc name;
struct sockaddr_ipc peer;
int minor;
size_t poolsz;
xnhandle_t handle;
char label[XNOBJECT_NAME_LEN];
struct rtdm_fd *fd; /* i.e. RTDM socket fd */
struct xddp_message *buffer;
int buffer_port;
struct xnheap *bufpool;
struct xnheap privpool;/*非系統內存池*/
size_t fillsz;
size_t curbufsz; /* Current streaming buffer size */
u_long status;
rtdm_lock_t lock;
nanosecs_rel_t timeout; /* connect()/recvmsg() timeout */
size_t reqbufsz; /* Requested streaming buffer size */
int (*monitor)(struct rtdm_fd *fd, int event, long arg);
struct rtipc_private *priv;
};
magic
用來區分該socket類型XDDP_SOCKET_MAGICname
綁定的rtipc套接字地址,peer
表示目標端口。minor
RTIPC端口號。privpool
XDDP本地內存池,僅供xddp通訊使用,其大小為poolsz
,用戶空間對該socket調用bind()
前可通過setsocket()
重復更其改大小,bind后無法更改。XDDP收發數據時的數據緩沖區可設置為從該區域分配,默認從xenomai的系統內存池cobalt_heap分配bufpool
數據緩沖區內存池指針,表示從哪個內存池分配數據緩沖區內存,如果設置了 XDDP本地內存池privpool
,則指向privpool
,否則指向xenomai系統內存池cobalt_heap。timeout
實時任務connect()/recvmsg()
超時時間reqbufsz
數據流緩沖區大小。fillsz
:緩沖區內的未讀數據長度;status
記錄XDDP socket 是否bind等狀態信息label
設置該socket的label,設置label后linux端可通過label來與該socket通訊。
這些設置與具體應用息息相關,了解低層原理后,結合具體應用場景來配置xdpp,能更好地發揮XDDP的性能。
4.setsocketopt函數配置XDDP
空間調用setsocketopt()
主要就是對這個結構體中的變量進行設置和修改,需要注意的是,在bind操作前設置才有效,等bind的時候,會按該結構內的資源設置情況進行分配,要多大內存的緩沖區 ,使用的端口是什么,通信過程中從哪里分配內存,這些都是在bind時確定的,而且確定后就不能更改了。
應用空間調用setsocketopt()來設置XDDP socktet,例如設置流緩沖區(XDDP_BUFSZ)大小1024字節。
streamsz = 1024
ret = setsockopt(s, SOL_XDDP, XDDP_BUFSZ,&streamsz, sizeof(streamsz));
與socket()函數一樣,是libcobalt庫中的函數:
/*lib\cobalt\rtdm.c*/
COBALT_IMPL(int, setsockopt, (int fd, int level, int optname, const void *optval,
socklen_t optlen))
{
struct _rtdm_setsockopt_args args = {
SOL_XDDP, XDDP_BUFSZ, (void *)&streamsz, 4
};
int ret;
ret = do_ioctl(fd, _RTIOC_SETSOCKOPT, &args);
if (ret != -EBADF && ret != -ENOSYS)
return set_errno(ret);
return __STD(s
與socket調用類似,先進行實時系統調用,如果參數非法,返回錯誤,才會轉而嘗試從glibc進行linux系統調用。在do_ioctl里直接進行實時系統調用sc_cobalt_ioctl
:
static int do_ioctl(int fd, unsigned int request, void *arg)
{
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);
ret = XENOMAI_SYSCALL3(sc_cobalt_ioctl, fd, request, arg);
pthread_setcanceltype(oldtype, NULL);
return ret;
}
實時系統調用sc_cobalt_ioctl
位於內核代碼kernel\xenomai\posix\io.c
:
COBALT_SYSCALL(ioctl, handover,
(int fd, unsigned int request, void __user *arg))
{
return rtdm_fd_ioctl(fd, request, arg);
}
int rtdm_fd_ioctl(int ufd, unsigned int request, ...)
{
struct rtdm_fd *fd;
fd = get_fd_fixup_mode(ufd);
....
va_start(args, request);
arg = va_arg(args, void __user *);
va_end(args);
set_compat_bit(fd);/*兼容32位應用處理*/
....
err = fd->ops->ioctl_rt(fd, request, arg);
...
rtdm_fd_put(fd);
....
return err;
}
第一個參數ufd是創建socket時返回的ufd,上一節已經與rtdm_fd聯系起來,所以直接通過get_fd_fixup_mode()
就能得到struct rtdm_fd
,進而獲取所有相關信息。
接着調用fd->ops->ioctl_rt,對於XDDP是xddp_ioctl()
。xddp_ioctl里首先判斷接着調用__xddp_ioctl
static int xddp_ioctl(struct rtdm_fd *fd,
unsigned int request, void *arg)
{
int ret;
......
ret = __xddp_ioctl(fd, request, arg);
}
return ret;
}
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_CONNECT): /*connect操作*/
ret = rtipc_get_sockaddr(fd, &saddrp, arg);
ret = __xddp_connect_socket(sk, saddrp);
break;
COMPAT_CASE(_RTIOC_BIND):/*bind操作*/
ret = rtipc_get_sockaddr(fd, &saddrp, arg);
.......
ret = __xddp_bind_socket(priv, saddrp);
break;
COMPAT_CASE(_RTIOC_GETSOCKNAME):/*獲取socket name*/
ret = rtipc_put_sockaddr(fd, arg, &sk->name);
break;
COMPAT_CASE(_RTIOC_GETPEERNAME):/*獲取socket name*/
ret = rtipc_put_sockaddr(fd, arg, &sk->peer);
break;
COMPAT_CASE(_RTIOC_SETSOCKOPT):/*配置socket*/
ret = __xddp_setsockopt(sk, fd, arg);
break;
COMPAT_CASE(_RTIOC_GETSOCKOPT):/*獲取socket配置*/
ret = __xddp_getsockopt(sk, fd, arg);
break;
case _RTIOC_LISTEN: /*不支持*/
ret = -EOPNOTSUPP;
break;
case _RTIOC_SHUTDOWN:
ret = -ENOTCONN;
break;
......
}
return ret;
}
__xddp_ioctl
主要根據request來進行操作,接着執行__xddp_setsockopt
進行具體配置:
static int __xddp_setsockopt(struct xddp_socket *sk,
struct rtdm_fd *fd,
void *arg)
{
int (*monitor)(struct rtdm_fd *fd, int event, long arg);
struct _rtdm_setsockopt_args sopt;
struct rtipc_port_label plabel;
struct timeval tv;
rtdm_lockctx_t s;
size_t len;
int ret;
ret = rtipc_get_sockoptin(fd, &sopt, arg);
......
if (sopt.level == SOL_SOCKET) {
switch (sopt.optname) {
case SO_RCVTIMEO:
ret = rtipc_get_timeval(fd, &tv, sopt.optval, sopt.optlen);
........
sk->timeout = rtipc_timeval_to_ns(&tv);
break;
......
}
return ret;
}
switch (sopt.optname) {
case XDDP_BUFSZ:/*配置buf size*/
........
break;
case XDDP_POOLSZ: /*設置POOLSZ大小 */
........
break;
case XDDP_MONITOR: /*設置 monitor 函數(僅內核應用支持)*/
......
break;
case XDDP_LABEL:/*設置 label*/
......
break;
default:
ret = -EINVAL;
}
return ret;
}
4.1 設置timeout
根據傳入的第2、3個參數來決定配置什么,先判斷是否是設置connect()/recvmsg()
超時時間,並設置。
4.2 設置流緩沖區大小:
上面說到XDDP提供了流緩沖功能,可以將多次發送的數據累積后作為整個數據包發送。XDDP_BUFSZ就是用來設置該緩沖區的最大大小的。
case XDDP_BUFSZ:
ret = rtipc_get_length(fd, &len, sopt.optval, sopt.optlen);
if (ret)
return ret;
if (len > 0) {
len += sizeof(struct xddp_message);
if (sk->bufpool &&
len > xnheap_get_size(sk->bufpool)) {/*大於可分配內存,返回錯誤*/
return -EINVAL;
}
}
rtdm_lock_get_irqsave(&sk->lock, s);
sk->reqbufsz = len;
if (len != sk->curbufsz &&
!test_bit(_XDDP_SYNCWAIT, &sk->status) &&
test_bit(_XDDP_BOUND, &sk->status))
ret = __xddp_resize_streambuf(sk); //多次分配,釋放原來的然后從xnheap 重新分配
rtdm_lock_put_irqrestore(&sk->lock, s);
break;
首先從用戶空間得到要設置的緩沖區大小保存到變量len,整個緩沖區為struct xddp_message
,由於數據累積期間需要一個message head來管理記錄緩沖區內數據的size、offset等信息,這個結構為struct xnpipe_mh
位於struct xddp_message
頭部,接着才是緩沖區的數據區,結構如下。
struct xnpipe_mh {
size_t size;
size_t rdoff;
struct list_head link;
};
struct xddp_message {
struct xnpipe_mh mh;
char data[];
};
由於默認從cobalt_heap中分配緩沖區內存,應用需要的緩沖區大小可能大於cobalt_heap的大小,所以建議先設置XDDP本地內存池,然后再配置緩沖區大小。
4.3 設置 XDDP使用的內存池
上面介紹過成員變量,sk->bufpool 數據緩沖區內存池指針,表示從哪個內存池分配數據緩沖區內存,如果設置了 XDDP本地內存池privpool,則指向privpool ,否則指向xenomai系統內存池cobalt_heap。下面看設置 XDDP本地內存池privpool:
case XDDP_POOLSZ:
ret = rtipc_get_length(fd, &len, sopt.optval, sopt.optlen);
......
cobalt_atomic_enter(s);
if (test_bit(_XDDP_BOUND, &sk->status) ||
test_bit(_XDDP_BINDING, &sk->status))
ret = -EALREADY;
else
sk->poolsz = len;
cobalt_atomic_leave(s);
break;
同樣處理傳入的參數,將要設置的內存池大小保存到len,判斷該socket是否已經bind,因為privpool管理的內存是在bind操作時才真正分配的,現在只是先記錄需要分配的大小。如果已經bind是不能再修改帶大小的。
4.4 設置XDDP label
除了使用固定端口外,還可通過設置xddp的socket label,linux可通過label來和該 XDDP socket通訊,設置label后bind時其RTIPC端口是系統自動分配的:
case XDDP_LABEL:
if (sopt.optlen < sizeof(plabel))
return -EINVAL;
if (rtipc_get_arg(fd, &plabel, sopt.optval, sizeof(plabel)))
return -EFAULT;
cobalt_atomic_enter(s);
if (test_bit(_XDDP_BOUND, &sk->status) ||
test_bit(_XDDP_BINDING, &sk->status))
ret = -EALREADY;
else {
strcpy(sk->label, plabel.label);
sk->label[XNOBJECT_NAME_LEN-1] = 0;
}
cobalt_atomic_leave(s);
break;
先進行參數檢查,然后將label拷貝到sk->label[]
中。
到此針對 xddp 的setsocketopt操作解析完畢,大部分操作為配置xddp_socket
這個結構體;