原文地址:http://blog.csdn.net/oneorzero/archive/2007/12/18/1948970.aspx
前一段时间,在开发一个驱动程序的过程中,需要在驱动程序与应用程序之间进行通信。其中驱动程序在接收到一个硬件中断之后通知应用程序进行相应的处理。为 解决此类问题,驱动程序提供了几种机制:(1)使用copy_to_user/copy_from_user方法,缺点是通信响应时间过长(2)使用信 号,但是限于字符设备(3)使用netlink。
在linux2.4之后引入了netlink机制,它将是Linux用户态与内核态交流的主要方法之一。netlink 的特点是对中断过程的支持,也就是说,可以在中断程序中直接调用netlink相关函数。它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线 程,而是通过另一个软中断调用用户事先指定的接收函数。netlink的通信过程如下:
下面分用户空间与内核空间2个部分讲述netlink的基本使用方法
1. 用户空间的程序
用户的应用程序使用标准套接字socket与内核空间进行通讯,标准socket API的函数, socket()、 bind()、sendmsg()、recvmsg() 和 close()很容易地应用到 netlink socket。
程序代码:
可以看到,应用程序流程与一般socket程序类似。
1) 调用socket创建套接字。
netlink对应的协议簇是 AF_NETLINK,第二个参数必须是SOCK_RAW或SOCK_DGRAM, 第三个参数指定netlink协议类型,它可以是一个自定义的类型,也可以使用内核预定义的类型,内核预定义的类型在文件include/linux/netlink.h中。
用户自定义的协议类型可以在此文件中加入。
2) 调用bind,绑定协议地址
bind函数需要绑定协议地址,netlink的socket地址使用struct sockaddr_nl结构描述:
struct sockaddr_nl
{
sa_family_t nl_family;
unsigned short nl_pad;
__u32 nl_pid;
__u32 nl_groups;
};
成员 nl_family为协议簇 AF_NETLINK,成员 nl_pad 当前没有使用,因此要总是设置为 0,成员 nl_pid 为接收或发送消息的进程的 ID,如果希望内核处理消息或多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。成员nl_groups 用于指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,如果设置为 0,表示调用者不加入任何多播组。
3) 调用sendto向内核发送消息
一个重要的问题就是发内核发送的消息的组成,使用我们发送一个IP网络数据包的话,则数据包结构为“IP包头+IP数据”,同样地,netlink的消息结构是“netlink消息头部+数据”。netlink消息头部使用struct nlmsghdr结构来描述:
struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message */
__u16 nlmsg_type; /* Message type*/
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process PID */
};
字段 nlmsg_len 指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,一般地,我们使用netlink提供的宏NLMSG_LENGTH来计算这个长度,仅需向NLMSG_LENGTH宏提供要发送的数据的长度,它会自动计算对齐后的总长度:
/*计算包含报头的数据报长度*/
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/*字节对齐*/
#define NLMSG_ALIGN(len) (((len)+NLMSG_ALIGNTO-1)&~(NLMSG_ALIGNTO-1) )
可以看到netlink提供很多宏,这些宏可以为我们编写netlink宏提供很大的方便。
字段 nlmsg_type 用于应用内部定义消息的类型,它对netlink内核实现是透明的,因此大部分情况下设置为0,字段nlmsg_flags用于设置消息标志,对于一般的使用,用户把它设置为0就可以,只是一些高级应用(如 netfilter和路由daemon需要它进行一些复杂的操作),字段nlmsg_seq和nlmsg_pid用于应用追踪消息,前者表示顺序号,后者为消息来源进程ID。
4) 调用recvfrom
当发送完请求后,就可以调用recv函数簇从内核接收数据了,接收到的数据包含了netlink消息首部和要传输的数据。程序中定义了如下的数据结构用来进行通信:
/*接收的数据包含了netlink消息首部和自定义数据结构*/
struct u_packet_info
{
struct nlmsghdr hdr;
struct packet_info p_info;
};
2. 内核空间的程序
与应用程序内核,内核空间也主要完成三件工作:
1) 创建netlink套接字
API函数netlink_kernel_create用于创建一个netlink socket,同时,注册一个回调函数,用于接收处理用户空间的消息。
struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));
参数unit表示netlink协议类型,如NETLINK_GENERIC,参数input则为内核模块定义的netlink消息处理函数,当有消息到达这个 netlink socket时,该input函数指针就会被引用。函数指针input的参数sk实际上就是函数 netlink_kernel_create返回的struct sock指针,sock实际是socket的一个内核表示数据结构,用户态应用创建的 socket在内核中也会有一个struct sock结构来表示。
2) 接收处理用户空间发送的数据
前面通过netlink_kernel_create注册的input函数对来自用户空间的数据进行处理。函数input()会在发送进程执行sendmsg()时被调用,这样处理消息比较及时,但是,如果消息特别长时,这样处 理将增加系统调用sendmsg()的执行时间,对于这种情况,可以定义一个内核线程专门负责消息接收,而函数input的工作只是唤醒该内核线程,这样 sendmsg将很快返回。
3) 发送数据至用户空间
在内核中,模块调用函数 netlink_unicast 来发送单播消息:
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);
参数sk为函数netlink_kernel_create()返回的socket,参数skb存放消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息,参数pid为接收消息进程的pid,参数nonblock表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠。
内核模块或子系统也可以使用函数netlink_broadcast来发送广播消息:
void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);
内核程序代码: