【原創】xenomai內核解析--xenomai與普通linux進程之間通信XDDP(一)--實時端socket建立流程

版權聲明:本文爲本文爲博主原創文章,轉載請註明出處。若有問題,歡迎指正。博客地址:https://www.cnblogs.com/wsg1100/html

1.概述

上篇文章xenomai內核解析--實時IPC概述中介紹了RTIPC,從這篇文章開始開始深刻xenomai內核,解析RTIPC的具體實現。linux

rtipc-arch

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端次設備號綁定,因此必須兩邊都關閉釋放了才能再次使用同一個端口(可見文末總框圖)。

下面咱們就沿着這個流程到內核裏面一探究竟,看看在內核裏面,都建立了哪些數據結構,作了哪些事情。數據結構

xddp-ipc

3.解析socket函數

從調用socket()函數開始。對於xenomai實時程序,該函數不是直接就執行系統調用,而是由xenomai實時庫libcobalt中實現,實時應用編譯時會連接到該庫。dom

/*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

建立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_STREAMSOCK_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_familysocket_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(&register_lock);

	xnid = xnid_fetch(&protocol_devices, id);
	if (xnid) {
		dev = container_of(xnid, struct rtdm_device, proto.id);
		__rtdm_get_device(dev);
	}
	mutex_unlock(&register_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_familysocket_type後,咱們的實時應用就能夠經過調用socket(),而後xenomai RTDM經過(protocol_family<<32) | socket_type做爲xnkey_t到該設備及對應的driver,來讓該設備爲咱們服務。

rdtm-rb

回到__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()執行完後各結構暫時以下:

rtdm_contex

接着執行ops.socket()也就是rtipc_socket(),傳入參數爲rtdm_fdprotocol(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
};

protols

接着根據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描述符的操做最後都是由實時設備驅動中具體函數來完成,後續的配置數據收發等都是按該路徑進行執行。

bind-xenosyscall

回到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來記錄着。

ddp-fds

將ufd與rtdm_fd聯繫起來之後,socket函數執行完畢,返回ufd,用戶空間經過ufd發起內核調用時,就可經過ufd找到內核裏相關的全部的結構。

xddp_socket

完成各數據結構分配關係連接後,下一步就能夠對該socket進行配置了。解析setsockopt()函數以前,上面圖中struct xddp_socketstruct 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_ppdCobalt_process.cobalt_ppd.cobalt_umm內的heap是每一個Cobalt進程私有的,除此以外xenomai內核中還有一個全局的struct cobalt_ppdcobalt_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_MAGIC
  • name綁定的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

根據傳入的第二、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[];
};

xddp_message

因爲默認從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這個結構體;

相關文章
相關標籤/搜索