xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程

简介: xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程

版权声明:本文为本文为博主原创文章,转载请注明出处。如有问题,欢迎指正。博客地址:https://www.cnblogs.com/wsg1100/

1.概述

上篇文章xenomai内核解析--实时IPC概述中介绍了RTIPC,从这篇文章开始开始深入xenomai内核,解析RTIPC的具体实现。

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中实现,实时应用编译时会链接到该库。

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

根据传入的第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[];
};

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这个结构体;

目录
相关文章
|
7天前
|
监控 安全 开发工具
鸿蒙HarmonyOS应用开发 | HarmonyOS Next-从应用开发到上架全流程解析
HarmonyOS Next是华为推出的最新版本鸿蒙操作系统,强调多设备协同和分布式技术,提供丰富的开发工具和API接口。本文详细解析了从应用开发到上架的全流程,包括环境搭建、应用设计与开发、多设备适配、测试调试、应用上架及推广等环节,并介绍了鸿蒙原生应用开发者激励计划,帮助开发者更好地融入鸿蒙生态。通过DevEco Studio集成开发环境和华为提供的多种支持工具,开发者可以轻松创建并发布高质量的鸿蒙应用,享受技术和市场推广的双重支持。
145 11
|
10天前
|
域名解析 弹性计算 安全
阿里云服务器租用、注册域名、备案及域名解析完整流程参考(图文教程)
对于很多初次建站的用户来说,选购云服务器和注册应及备案和域名解析步骤必须了解的,目前轻量云服务器2核2G68元一年,2核4G4M服务器298元一年,域名注册方面,阿里云推出域名1元购买活动,新用户注册com和cn域名2年首年仅需0元,xyz和top等域名首年仅需1元。对于建站的用户来说,购买完云服务器并注册好域名之后,下一步还需要操作备案和域名绑定。本文为大家展示阿里云服务器的购买流程,域名注册、绑定以及备案的完整流程,全文以图文教程形式为大家展示具体细节及注意事项,以供新手用户参考。
|
27天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
54 12
|
1月前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
71 4
|
1月前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
1月前
|
算法 调度
探索操作系统的心脏:内核与进程管理
【10月更文挑战第25天】在数字世界的复杂迷宫中,操作系统扮演着关键角色,如同人体中的心脏,维持着整个系统的生命力。本文将深入浅出地剖析操作系统的核心组件——内核,以及它如何通过进程管理来协调资源的分配和使用。我们将从内核的概念出发,探讨它在操作系统中的地位和作用,进而深入了解进程管理的机制,包括进程调度、状态转换和同步。此外,文章还将展示一些简单的代码示例,帮助读者更好地理解这些抽象概念。让我们一起跟随这篇文章,揭开操作系统神秘的面纱,理解它如何支撑起我们日常的数字生活。
|
2月前
|
网络协议 机器人 C++
KUKA机器人Socket通讯配置方法:技术干货分享
【10月更文挑战第7天】在现代自动化生产线上,KUKA机器人凭借其高效、灵活和精确的特点,成为众多企业的首选。为了实现KUKA机器人与其他设备或系统之间的数据交互,Socket通讯配置显得尤为重要。本文将详细介绍KUKA机器人Socket通讯的配置方法,帮助大家在工作中更好地掌握这一技术。
314 2
|
2月前
|
JavaScript 前端开发 UED
Vue执行流程及渲染解析
【10月更文挑战第5天】
|
2月前
|
存储 搜索推荐 数据库
运用LangChain赋能企业规章制度制定:深入解析Retrieval-Augmented Generation(RAG)技术如何革新内部管理文件起草流程,实现高效合规与个性化定制的完美结合——实战指南与代码示例全面呈现
【10月更文挑战第3天】构建公司规章制度时,需融合业务实际与管理理论,制定合规且促发展的规则体系。尤其在数字化转型背景下,利用LangChain框架中的RAG技术,可提升规章制定效率与质量。通过Chroma向量数据库存储规章制度文本,并使用OpenAI Embeddings处理文本向量化,将现有文档转换后插入数据库。基于此,构建RAG生成器,根据输入问题检索信息并生成规章制度草案,加快更新速度并确保内容准确,灵活应对法律与业务变化,提高管理效率。此方法结合了先进的人工智能技术,展现了未来规章制度制定的新方向。
50 3