星期六, 五月 19, 2007

想过吗?进程怎么样与内核比如说中断 通信呢 ?

这个问题不是我提出来的,这是我一群友提出来的。 进程间通信大家肯定能说出好多的方法,比如共享内存,socket,管道,信号两等等。可有没有想过 内核怎么样与进程间通讯呢 ?
比如说,在用户空间有一个进程要等待 I/O空间的输入,假如 外设有输入,进程就需要一定的响应,但这个消息内核如何通知用户进程呢 ? 我们知道,用户空间与内核空间的数据传送多用copy_to_user()/ copy_from_user()一类函数来实现的。我也不知道,现在转一下别人的文章

----------------------------转载于别处,谢谢这位大哥啦-------------------------------
☆─────────────────────────────────────☆
chex (继续努力) 于 (Fri Jan 2 22:15:19 2004) 提到:

不久前在几个学校bbs的linux版上闲逛,学会了不少东西,也发现了不少的问题。
在水木上就看到了有同学问linux内核态与用户态通信的问题,感觉还是蛮重要的,特别
是OS思想,所以在这里总结一下自己在这方面的经验。
首先,linux是运行在保护模式下的操作系统,所以用户态和内核态的存储区不可直
接互相访问。这就需要通过额外的手段来实现它们的通信。
其次,在linux内核态运行的代码有两种运行方式:kernel thread和tasklet。因为
tasklet是软中断触发的,所以不可睡眠,而kernel thread则在运行状态上同用户态的
进程相似,可以睡眠。
在实现通信前,我们需要了解通信一端的内核代码是什么运行方式。如果是kernel
thread,则好办得多。我们可以通过增加特殊设备的驱动程序(增加系统调用)或使用
类似setsockopt()/getsockopt()这类可以在内核中注册处理过程的方法来实现。具体可
以参见iptables或一段字符设备的驱动程序。它们实现通信的关键在于copy_to_user()/
copy_from_user()一类函数来实现用户态与内核态的内存拷贝,但此类函数会引发睡眠
,所以不能用在tasklet中。
如果内核端代码的运行方式是tasklet,则可以通过unix domain socket或netlink
socket来实现。因为unix domain socket程序在很多书中都有,所以在这里不加说明。
我们主要是来看一下如何使用netlink socket实现通信。
在linux-2.4的代码中有ipqueue这个模块,它就是用netlink socket来实现tasklet
与用户态进程通信的(源码在ip_queue.c中),但其代码较庞大,而且很多功能我们不
一定使用,所以在这里写了一段简单的代码框架,它使用netlink socket实现通信。
/* user space */
int main()
{
int sk;
struct sockaddr_nl local;
struct sockaddr_nl peer;

sk = socket(PF_NETLINK, SOCK_RAW, NETLINK_FIREWALL);

memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid(); /* 用户态端绑定本进程pid */
local.nl_groups = 0;
bind(sk, (struct sockaddr *)local, sizeof(local));

memset(&peer, 0, sizeof(peer));
peer.nl_family = AF_NETLINK;
peer.nl_pid = 0; /* 内核态端的netlink套接字绑定的pid是0 */
peer.nl_groups = 0;

/* netlink封包的格式必须以nlmsghdr结构变量开头,以下是最简单的实现。 */
/*
struct nlmsg
{
struct nlmsghdr hdr;
} message;
*/

memset(&message, 0, sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(0);
message.hdr.nlmsg_flags = 0;
message.hdr.nlmsg_type = 0x10; /* 自定义类型 */
message.hdr.nlmsg_pid = local.nl_pid;

sendto(sk, &message, message.hdr.nlmsg_len, 0,
(struct sockaddr*)&peer, sizeof(peer));
/* 向内核态发送本地进程的信息 */
}

nlmsghdr结构体分析:
struct nlmsghdr
{
__u32 nlmsg_len; /* 封包长度(包括消息头) */
__u16 nlmsg_type; /* 消息类型(可自定义) */
__u16 nlmsg_flags; /* 附加标志 */
__u32 nlmsg_seq; /* 序列号 */
__u32 nlmsg_pid; /* 发送进程ID */
};
如此便在用户态创建了一个netlink套接字。发送和接受数据的函数分别为sendto()
和recvfrom()。

内核态创建netlink套接字的过程:
struct sock *nfnl = NULL;
……
{
……
nfnl = netlink_kernel_create(NETLINK_FIREWALL, netlink_receive);
……
}

static DECLARE_MUTEX(sqnl_sem); /* 定义锁 */
/* 用来接收用户态进程传来数据的函数。 */
static void netlink_receive(struct sock *sk, int len)
{
do
{
struct sk_buff *skb;
if(down_trylock(&sqnl_sem))
return;
while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
{
{
struct nlmsghdr *nlh;

if(skb->len >= sizeof(struct nlmsghdr))
{
nlh = (struct nlmsghdr*)skb->data;
if(nlh->nlmsg_len >= sizeof(struct nlmsghdr)
&& skb->len >= nlh->nlmsg_len)
{
……
/* 保存相应的pid */
……
}
}
}
kfree_skb(skb);
}
up(&sqnl_sem);
}while(nfnl && nfnl->receive_queue.qlen);
}
这里sqnl_sem锁的作用大家来讨论一下吧,还有,这里最后的while,又对nfnl的接收队列做了判断,不知各位的看法如何?(因为我的想法还不成熟,不好意思说出来)

内核态发送消息的代码框架:
struct message
{
char buf[16]; /* 要传送的信息 */
};

……
{
unsigned char *old_tial;
int size;
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct message *pmsg;

/* 分配一个数据长度为(消息长度+消息头)并4字节对齐后的skb */
size = NLMSG_SPACE(sizeof(message));
skb = alloc_skb(size,GFP_ATOMIC);
old_tail = skb->tail; /* 记录当前octet尾部位置 */

nlh = NLMSG_PUT(skb, 0, 0, 0x11, size – sizeof(*nlh));
pmsg = NLMSG_DATA(nlh);
strcpy(pmsg->buf,”MESSAGE”);
nlh->nlmsg_len = skb->tail – old_tail;

/* 这里需要设置,因为alloc_skb宏并不会为新开辟的缓存清0 */
NETLINK_CB(skb).dst_groups = 0;
return netlink_unicast(nfnl, skb, userpid, MSG_DONTWAIT);

nlmsg_failure:
if(skb)
{
kfree_skb(skb);
}
……
return -1;
}

以上代码(运行在i386计算机上)用到了不少文件linux/netlink.h中定义的宏,大
家可以看该文件参考一下,我感觉这些宏很好用,所以在这里使用了它们。以上还有很多
值得讨论的地方,希望大家不要客气:)

----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------------------------

不知道他的想法对不对,大家假如有好的办法可以留言给我,或者邮件通知我 gaominggm#gmail.com 谢谢啊 !!!!!!

没有评论: