本文原创为freas_1990,转载请标明出处:http://blog.csdn.net/freas_1990/article/details/10162853

Jack:我想知道用户如何把数据发送到内核空间的?

我:你觉得哪里比较难理解呢?

Jack:一般程序员会在程序里通过socket变量获得一个文件描述符,然后通过write把定义好的字符串写入到该描述符。

我:是的。你有什么不明白的吗?

Jack:可是,我不知道这个write底层到底会做什么。

我:这个write底层会调用sock_send函数。我给你看一下这个函数的定义。

static int
sock_send(int fd, void * buff, int len, unsigned flags)
{
struct socket *sock;
struct file *file; DPRINTF((net_debug,
"NET: sock_send(fd = %d, buff = %X, len = %d, flags = %X)\n",
fd, buff, len, flags)); if (fd < 0 || fd >= NR_OPEN || ((file = current->filp[fd]) == NULL))
return(-EBADF);
if (!(sock = sockfd_lookup(fd, NULL))) return(-ENOTSOCK); return(sock->ops->send(sock, buff, len, (file->f_flags & O_NONBLOCK), flags));
}

sock_send函数通过用户传入的socket描述符fd找到对应的struct socket结构,然后把找到的socket结构。然后把socket结构(sock),buff(这是一个逻辑地址),以及文件flag传入传输层的对应函数。

最后一个语句return调用了一个函数指针(
这就是函数指针的妙处!),这个函数指针如果对应下面的传输层协议是UDP协议,就会调用udp_sendto.

static int
udp_write(struct sock *sk, unsigned char *buff, int len, int noblock,
 unsigned flags)
{
  return(udp_sendto(sk, buff, len, noblock, flags, NULL, 0));
}

其实是一个包裹函数。干活儿的是udp_sendto。

static int
udp_sendto(struct sock *sk, unsigned char *from, int len, int noblock,
  unsigned flags, struct sockaddr_in *usin, int addr_len)
{
  struct sockaddr_in sin;
  int tmp;
  int err;   DPRINTF((DBG_UDP, "UDP: sendto(len=%d, flags=%X)\n", len, flags));   /* Check the flags. */
  if (flags) 
  return(-EINVAL);
  if (len < 0) 
  return(-EINVAL);
  if (len == 0) 
  return(0);   /* Get and verify the address. */
  if (usin) {
if (addr_len < sizeof(sin)) return(-EINVAL);
err=verify_area(VERIFY_READ, usin, sizeof(sin));
if(err)
return err;
memcpy_fromfs(&sin, usin, sizeof(sin));
if (sin.sin_family && sin.sin_family != AF_INET) 
return(-EINVAL);
if (sin.sin_port == 0) 
return(-EINVAL);
  } else {
if (sk->state != TCP_ESTABLISHED) return(-EINVAL);
sin.sin_family = AF_INET;
sin.sin_port = sk->dummy_th.dest;
sin.sin_addr.s_addr = sk->daddr;
  }
  
  if(!sk->broadcast && chk_addr(sin.sin_addr.s_addr)==IS_BROADCAST)
    return -EACCES;/* Must turn broadcast on first */
  sk->inuse = 1;   /* Send the packet. */
  tmp = udp_send(sk, &sin, from, len);   /* The datagram has been sent off.  Release the socket. */
  release_sock(sk);
  return(tmp);
}

这其实也是一个包裹函数,真正干活的是udp_send函数。

static int
udp_send(struct sock *sk, struct sockaddr_in *sin,
unsigned char *from, int len)
{
struct sk_buff *skb;
struct device *dev;
struct udphdr *uh;
unsigned char *buff;
unsigned long saddr;
int size, tmp;
int err; DPRINTF((DBG_UDP, "UDP: send(dst=%s:%d buff=%X len=%d)\n",
in_ntoa(sin->sin_addr.s_addr), ntohs(sin->sin_port),
from, len)); err=verify_area(VERIFY_READ, from, len);
if(err)
return(err); /* Allocate a copy of the packet. */
size = sizeof(struct sk_buff) + sk->prot->max_header + len;
skb = sk->prot->wmalloc(sk, size, 0, GFP_KERNEL);
if (skb == NULL) return(-ENOMEM); skb->mem_addr = skb;
skb->mem_len = size;
skb->sk = NULL; /* to avoid changing sk->saddr */
skb->free = 1;
skb->arp = 0; /* Now build the IP and MAC header. */
buff = skb->data;
saddr = 0;
dev = NULL;
DPRINTF((DBG_UDP, "UDP: >> IP_Header: %X -> %X dev=%X prot=%X len=%d\n",
saddr, sin->sin_addr.s_addr, dev, IPPROTO_UDP, skb->mem_len));
tmp = sk->prot->build_header(skb, saddr, sin->sin_addr.s_addr,
&dev, IPPROTO_UDP, sk->opt, skb->mem_len,sk->ip_tos,sk->ip_ttl);
skb->sk=sk; /* So memory is freed correctly */ if (tmp < 0 ) {
sk->prot->wfree(sk, skb->mem_addr, skb->mem_len);
return(tmp);
}
buff += tmp;
saddr = dev->pa_addr;
DPRINTF((DBG_UDP, "UDP: >> MAC+IP len=%d\n", tmp)); skb->len = tmp + sizeof(struct udphdr) + len; /* len + UDP + IP + MAC */
skb->dev = dev;
#ifdef OLD
/*
* This code used to hack in some form of fragmentation.
* I removed that, since it didn't work anyway, and it made the
* code a bad thing to read and understand. -FvK
*/
if (len > dev->mtu) {
#else
if (skb->len > 4095)
{
#endif
printk("UDP: send: length %d > mtu %d (ignored)\n", len, dev->mtu);
sk->prot->wfree(sk, skb->mem_addr, skb->mem_len);
return(-EMSGSIZE);
} /* Fill in the UDP header. */
uh = (struct udphdr *) buff;
uh->len = htons(len + sizeof(struct udphdr));
uh->source = sk->dummy_th.source;
uh->dest = sin->sin_port;
buff = (unsigned char *) (uh + 1); /* Copy the user data. */
memcpy_fromfs(buff, from, len); /* Set up the UDP checksum. */
udp_send_check(uh, saddr, sin->sin_addr.s_addr, skb->len - tmp, sk); /* Send the datagram to the interface. */
sk->prot->queue_xmit(sk, dev, skb, 1); return(len);
}

这个函数里真正干活的是memcpy_fromfs函数,执行完了这个函数,数据就已经从用户空间拷贝到内核空间了。

之后的sk->prot->queue_xmit(sk, dev, skb, 1);通过函数指针把sk上的skb这个数据包排入发送队列。

Linux内核源代码解析——用户发送数据包的起源之sendto的更多相关文章

  1. Linux内核源代码解析之——sock's buffer参数

    本文原创为freas_1990,转载请标明出处:http://blog.csdn.net/freas_1990/article/details/11539695 关于socket与sock的关系再简单 ...

  2. Linux内核源代码解析——TCP状态转移图以及其实现

    本文原创为freas_1990,转载请标明出处http://blog.csdn.net/freas_1990/article/details/10223581 TCP状态转移的原理并不高深,但是处理逻 ...

  3. Linux内核源代码解析之——我与神童聊Linux内核

    本文原创为freas_1990,转载请标明出处:http://blog.csdn.net/freas_1990/article/details/11619609 我的朋友里,至少有2.5个神童. 有的 ...

  4. Linux内核源代码解析之TCP面向字节流

    本文原创为freas_1990,转载请标明出处:http://blog.csdn.net/freas_1990/article/details/11264237 大家都知道TCP是面向stream,而 ...

  5. 多队列网卡简介以及Linux通过网卡发送数据包源码解读

    http://blog.csdn.net/yanghua_kobe/article/details/7485254 首先我们看一下一个主流多队列网卡(E1000)跟多核CPU之间的关系图: 非多队列: ...

  6. Linux网络之设备接口层:发送数据包流程dev_queue_xmit

    转自:http://blog.csdn.net/wdscq1234/article/details/51926808 写在前面 本文主要是分析kernel-3.8的源代码,主要集中在Network的n ...

  7. Linux内核源代码情景分析系列

    http://blog.sina.com.cn/s/blog_6b94d5680101vfqv.html Linux内核源代码情景分析---第五章 文件系统  5.1 概述 构成一个操作系统最重要的就 ...

  8. linux内核空间与用户空间信息交互方法

    linux内核空间与用户空间信息交互方法     本文作者: 康华:计算机硕士,主要从事Linux操作系统内核.Linux技术标准.计算机安全.软件测试等领域的研究与开发工作,现就职于信息产业部软件与 ...

  9. TCP和UDP 协议发送数据包的大小

    在进行UDP编程的时候,我们最容易想到的问题就是,一次发送多少bytes好? 当然,这个没有唯一答案,相对于不同的系统,不同的要求,其得到的答案是不一样的,这里仅对像ICQ一类的发送聊天消息的情况作分 ...

随机推荐

  1. 什麼是 N-key 與按鍵衝突?原理說明、改善技術、選購注意完全解析

    不管是文書處理或遊戲中,我們都經常會使用到組合鍵,也就是多顆按鍵一起按下,執行某些特定的功能.有時候你可能會發現,明明只按下2顆鍵,再按下第3顆鍵時訊號卻沒有輸出.要是打報告到一半遇到這種狀況還好,如 ...

  2. Wikioi 1294 全排列

    先给出链接地址:Wikioi 1294 虽然题目很短,论难度也就是个深搜,算法方面我就不多说了,而且我知道c++有个函数叫next_permutation,谁用谁知道. 代码如下: #include& ...

  3. RPATH与RUNPATH

    RPATH与RUNPATH 时间 2011-11-01 21:46:44 Qt Labs China 原文  http://labs.qt.nokia.com.cn/2011/11/01/rpath- ...

  4. Java中的import

    有些人写了一阵子 Java,可是对于 Java的 package 跟 import 还是不太了解.很多人以为原始码 .java 文件中的 import 会让编译器把所 import 的程序通通写到编译 ...

  5. hdu 4753 Fishhead’s Little Game

    状态压缩dp解博弈问题(记忆化搜索).比赛的时候最后才开始做这道题,而且当时不知道为什么一直犯一些很2B的问题,导致没能ac,晚上看了看原先的代码,改了一下就MLE了...我原先是开的dp[1 < ...

  6. 【(阶乘的质因数分解)算组合数】【TOJ4111】【Binomial efficient】

    n<=10^6 m<=10^6 p=2^32 用unsigned int 可以避免取模 我写的SB超时 阶乘分解代码 #include <cstdio> #include &l ...

  7. JavaScript跨域深入研究与解决办法(转)

    什么是跨域 JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象.但在安全限制的同时也给注入iframe或是ajax应用上带来了不少麻烦.这里把涉及到跨域的一些问题简单地整理一下: 首 ...

  8. 标准C++的vector使用

    原文:http://blog.csdn.net/pandy1110/article/details/5963908 C++内置的数组支持容器的机制,但是它不支持容器抽象的语义.要解决此问题我们自己实现 ...

  9. MySQL如何使用索引 较为详细的分析和例子

    在数据库表中,使用索引可以大大提高查询速度. 假如我们创建了一个 testIndex 表: CREATE TABLE testIndex(i_testID INT NOT NULL,vc_Name V ...

  10. Web页面在手机上显示过大问题

    网上抄来了,自己也备忘下:增加<meta name="viewport" content="width=device-width, initial-scale=1. ...