当前位置:首页 > > 充电吧
[导读]内核态与用户态通信方式 Linux下内核空间与用户空间进行通信的方式主要有system call、sysctl、procfs、模块参数、debugfs、relayfs、sysfs和netlink等。

内核态与用户态通信方式

Linux下内核空间与用户空间进行通信的方式主要有system call、sysctl、procfs、模块参数、debugfs、relayfs、sysfs和netlink等。

Why NetlinK full-duplex

模块参数、sysfs、procfs、debugfs和relayfs都是基于文件系统,用于内核向用户发送消息;sysctl和system call是用户向内核发送消息。它们都是单工通信方式。netlink是一种特殊的通信方式,用于在内核空间和用户空间传递消息,是一种双工通信方式。使用地址协议簇AF_NETLINK,使用头文件include/linux/netlink.h。

simple to add
为新特性添加system call、sysctl或者procfs是一件复杂的工作,它们会污染kernel,破坏系统的稳定性,这是非常危险的。Netlink的添加,对内核的影响仅在于向netlink.h中添加一个固定的协议类型,然后内核模块和应用层的通信使用一套标准的API。 Netlink Socket APIs 用户空间 创建
int socket(int doamin, int type, int protocal);

domain填AF_NETLINK,type为SOCK_RAW或者SOCK_DGRAM。protocal可选NETLINK_ROUTE、NETLINK_FIREWALL、NETLINK_ARPD、NETLINK_ROUTE6和NETLINK_IP6_FW,或者传入自定义协议类型。

绑定
int bind(int fd, (struct sockaddr *)&nladdr, sizeof(nladdr));

netlink地址结构如下

struct sockaddr_nl{
    sa_family_t     nl_family;      //AF_NETLINK
    unsigned short  nl_pad;         //0
    __u32           nl_pid;         //进程pid
    __u32           nl_groups;      //多播组掩码
}

如果进程仅仅使用一个netlink,nl_pid可以传入该进程的pid,

my_pid = getpid();

如果一个进程的多个线程需要不同的netlink,使用如下方法获得nl_pid:

pthread_self << 16 | gitpid();
发送
int sendmsg(fd, &msg, 0);

struct nlmsghdr msg为netlink发送消息结构,

struct msghdr{
    void            *msg_name;      //socket name
    int             msg_namelen;    //len of name
    struct iovec    *msg_iov;       //data block
    __kernel_size_t msg_iovlen;     //num of block
    void            *msg_control;   //per protocol magic
    __kernel_size_t msg_controllen; //len of cmsg list
    unsigned        msg_flags;

}

其中需要填充的是msg_iov和msg_oivlen。
msg与其他结构体的关系如图所示

nlmsghdr结构体为消息头信息

struct nlmsghdr{
    __u32 nlmsg_len;    //len of msg
    __u16 nlmsg_type;   //msg type
    __u16 nlmsg_flags;  //additional flags
    __u32 nlmsg_seq;    //sequence num
    __u32 nlmsg_pid;    //send process pid
}

nlmsg_len是整个消息的长度,包含头;
nlmsg_type对内核不透明;
nlmsg_flag被用于对消息的附加控制;
nlmsg_seq和nlmsg_pid被用于跟踪消息,对内核不透明。

要将地址结构体传入消息头部:

struct msghdr msg;
msg.msg_name = (void *)&nladdr;
msg.msg_namelen = sizeof(nladdr);

发送地址同样使用sockaddr_nl结构体,如果目的是内核,nl_pid和nl_groups都为0;如果消息为单播,目的为另一个进程,nl_pid是目的进程pid,nl_groups为0;如果消息为多播,目的是一个或多个多播组,nl_groups域中需填写所有目的多播组的掩码和。

结构体iovce结构如下

struct iovec iov;
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
接收

接收时需要创建足够的buf,

struct sockadddr_nl nladdr;
struct msghdr msg;
struct iovec iov;

iov.iov_base = (void *)nlh;
iov.iov_len = MAX_NL_MSG_LEN;
msg.msg_name = (void *)&nladdr;
msg.msg_namelen = sizeof(nladdr);

msg.msg_iov = &iov;
msg.msg_iovlen = 1;
recvmsg(fd, &msg, 0);
内核空间

内核空间的API由net/core/af_netlink.c支持。
可以在netlink.h中添加自定义协议类型:

#define NETLINK_TEST    17
创建
struct sock *netlink_kernel_create(int unit, 
        void (*input)(struct sock *sk, int len));

unit是netlink.h中定义的协议类型,*input是一个回调函数指针,接收到消息会触发这个函数。

发送

发送消息使用struct sk_buff *skb。
用以下方式设置本地地址:

NETLINK_CB(skb).groups = local_groups;
NETLINK_CB(skb).pid = 0;

因为是在内核,pid = 0

用以下方式设置目的地址:

NETLINK_CB(skb).dst_groups = dst_groups;
NETLINK_CB(skb).dst_pid = dst_pid;

发送单播函数为

int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);

*sk为netlink_kernel_create()返回的结构体指针;
skb->data指向消息;
pid为应用程序的pid;
block表明如果当接收缓冲非法时数据分块还是返回错误值。

发送多播函数为

void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);

group是所有需要接收多播消息的组掩码和;
allocation是内核内存分配标志,中断上下文中使用GFP_ATOMIC,其他情况使用GFP_KERNEL。

销毁
sock_release(nl_sk->socket);
example user-space 初始化
#define MAX_NLMSG_PAYLOAD_LEN       128
#define NETLINK_GROUP_MASK(group)   (1 << group)

int usr_netlink_init(int netlink_type, int *fd, int group)
{
    struct nlmsghdr *nlh = NULL;
    struct sockaddr_nl addr;

    memset((void *)&addr, 0x0, sizeof(addr));

    if ((*fd = socket(PF_NETLINK, SOCK_RAW, netlink_type)) < 0)
    {
        /* error process*/
        return -1;
    }

    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    addr.nl_groups = NETLINK_GROUP_MASK(group);

    if (bind(*fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        /* error process */
        close(*fd);
        *fd = -1;
        return -2;
    }

    /* send pid to kernel */
    if (us_netlink_send(*fd, 0, NULL, 0) < 0)
    {
        /* error process */
        close(*fd);
        *fd = -1;
        return -3;
    }

    return 0;
}
发送
int usr_netlink_send(int fd, uint16 msg_type, char *data, int data_len)
{
    struct nlmsghdr *nlh;
    struct msghdr msg;
    struct iovec iov;
    int msg_len;

    if (data && (msg_len > MAX_NLMSG_PAYLOAD_LEN))
    {
        return -1;
    }

    memset((void*)&iov, 0x0, sizeof(iov));
    memset((void*)&msg, 0x0, sizeof(msg));        
    msg_len = NLMSG_SPACE(data_len);

    nlh = (struct nlmsghdr *)malloc(msg_len);
    if (NULL = nlh)
    {
        return -2;
    }

    /* fill nlmsghdr */
    memset(nlh, 0x0, msg_len);
    nlh->nlmsg_len = msg_len;
    nlh->nlmsg_pid = getpid();
    nlh->nlmsg_type = msg_type;
    nlh->nlmsg_flags = 0;

    if (data)
    {
        memcpy(NLMSG_DATA(nlh), data, msg_len);
    }

    /* fill iovec */
    iov.iov_base = (void *)nlh;
    iov.iov_len = nlh->nlmsg_len;
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    if (sendmsg(fd, &msg, 0) < 0)
    {
        /* error process */
        free(nlh);
        return -3;
    }

    free(nlh);
    return 0;
}
接收
int usr_netlink_recv(int fd, char *buf, int len)
{
    struct iovec iov = {buf, len};
    struct sockaddr_nl nl_src_addr;
    struct msghdr msg;
    int recv_len;

    memset(&msg, 0x0, sizeof(struct msghdr));
    memset(&nl_src_addr, 0x0, sizeof(struct sockaddr_nl));

    msg.msg_name = (void *)&nl_src_addr;
    msg.msg_namelen = sizeof(nl_src_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    recv_len = recvmsg(fd, &msg, 0);
    if (recv_len < 0)
    {
        /* recv error */
    }
    else if (recv_len == 0)
    {
        /* recv EOF */
    }

    return recv_len;
}
kernel 初始化
static struct sock *sg_knl_xxx_sk = NULL;
static int sg_knl_xxx_pid = -1;

void knl_netlink_init()
{   
    struct net init_net;

    sg_knl_xxx_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 0, knl_neklink_recv, NULL, THIS_MODULE);
}
接收
void knl_netlink_recv(struct sock *skb)
{
    struct nlmsghdr *nlh = nlmsg_hdr(skb);
    sg_knl_xxx_pid = nlh->nlmsg_pid;
}
发送
int group_mask(int group)
{
    return (1 << group);
}

void knl_netlink_send(int msg_type, char *data, int data_len, int group)
{
    struct sk_buff *skb = NULL;
    struct nlmsghdr *nlh = NULL;
    unsigned int msg_len = NLMSG_SPACE(data_len);

    if(data && (data_len > MAX_PAYLOAD_LEN))
    {
        /* error process */
        return;
    } 

    skb = alloc_skb(msg_len, GFP_KERNEL);
    if (!skb)
    {
        /* erro process */
        return;
    }

    memset((void *)skb, 0x0, msg_len);
    nlh = nlmsg_put(skb, 0, 0, msg_type, msg_len, 0);

    if(data)
    {
        memcpy(NLMSG_DATA(nlh), data, data_len);
    } 

    NETLINK_CB(skb).pid = 0; /*from kernel */
    NETLINK_CB(skb).dst_group = group_mask(group); 

    /* if unicast */
    netlink_unicast(sg_knl_xxx_sk, skb, sg_knl_xxx_pid, MSG_DONTWAIT);
    /* if mulicast */
    //nlmsg_multicast(sg_knl_xxx_sk, skb, 0, group_mask(group), 0);
}
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭