Linux网络子系统之 raw socket 分析 一 recvfrom过程

文章详细阐述了在Linux系统中,使用RAWsocket进行数据接收的过程,包括用户层的recvfrom调用,系统调用的处理,如__sys_recvfrom函数,以及后续的packet_recvmsg和skb_recv_datagram等步骤。过程中涉及了缓冲区管理、阻塞等待和线程调度等关键环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RAW socket接收数据过程

1、用户层接收逻辑

bytes = recvfrom(fd, buf, length, NULL, NULL)

使用recvfrom接口,参数与udp类似。

2、系统调用解析

SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
        unsigned int, flags, struct sockaddr __user *, addr,
        int __user *, addr_len)
{
    return __sys_recvfrom(fd, ubuf, size, flags, addr, addr_len);
}

2.1、__sys_recvfrom分析
int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,struct sockaddr __user *addr, int __user *addr_len)
{
	err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);//1
	sock = sockfd_lookup_light(fd, &err, &fput_needed);//2
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
	msg.msg_name = addr ? (struct sockaddr *)&address : NULL;//3
    if (sock->file->f_flags & O_NONBLOCK)
        flags |= MSG_DONTWAIT;//4
	err = sock_recvmsg(sock, &msg, flags);//5
}

1.将用户态接收buff挂到msg.msg_iter上:

	iov->iov_base = buf;
	iov->iov_len = len;
iov_iter_init(i, rw, iov, 1, len);
{
	i->iov = iov;
}

最终&msg.msg_iter->iov = iov;
2.根据fd查找socket
3.如果传入的addr为NULL,则给msg.msg_name赋NULL。
4.socket默认为阻塞模式,因此不会赋值MSG_DONTWAIT
5.调用sock_recvmsg

2.2、packet_recvmsg分析
sock->ops->recvmsg = packet_recvmsg
int packet_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
              int flags)
{
	struct sock *sk = sock->sk;
    struct sk_buff *skb;
	skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &err);//1
	copied = skb->len;
	err = skb_copy_datagram_msg(skb, 0, msg, copied);//2
}

1.接收数据,返回skb

struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned int flags,
                  int noblock, int *err)
{
    int off = 0;
    return __skb_recv_datagram(sk, &sk->sk_receive_queue,
                   flags | (noblock ? MSG_DONTWAIT : 0),//1
                   &off, err);
}

struct sk_buff *__skb_recv_datagram(struct sock *sk,
                    struct sk_buff_head *sk_queue,
                    unsigned int flags, int *off, int *err)
{
    struct sk_buff *skb, *last;
    long timeo;
    timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);//2
    do {
        skb = __skb_try_recv_datagram(sk, sk_queue, flags, off, err,
                          &last);//3
        if (skb)
            return skb;
        if (*err != -EAGAIN)
            break;
    } while (timeo &&
         !__skb_wait_for_more_packets(sk, sk_queue, err,
                          &timeo, last));//4

    return NULL;
}

1.这里传入的flags = 0
2.返回sk->sk_rcvtimeo, sock创建时,sk->sk_rcvtimeo赋值为极大值,相当于阻塞。
3.接收数据包,读取 sk-> sk_receive_queue 接收队列。
4. __skb_wait_for_more_packets 会令用户进程休眠。

struct sk_buff *__skb_try_recv_datagram(struct sock *sk,
                    struct sk_buff_head *queue,
                    unsigned int flags, int *off, int *err,
                    struct sk_buff **last)
{
	struct sk_buff *skb;
    spin_lock_irqsave(&queue->lock, cpu_flags);//加锁
    skb = __skb_try_recv_from_queue(sk, queue, flags, off,&error,last);
    spin_unlock_irqrestore(&queue->lock, cpu_flags);//解锁
}

struct sk_buff *__skb_try_recv_from_queue(struct sock *sk,
                      struct sk_buff_head *queue,
                      unsigned int flags,
                      int *off, int *err,
                      struct sk_buff **last)
{
    struct sk_buff *skb;
	skb_queue_walk(queue, skb)//获取接收队列头的next节点,即最早的skb
}

2.3、阻塞等待数据到来
int __skb_wait_for_more_packets(struct sock *sk, struct sk_buff_head *queue, int *err, long *timeo_p, const struct sk_buff *skb)
{
	DEFINE_WAIT_FUNC(wait, receiver_wake_function);//1
	prepare_to_wait_exclusive(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);//2
	*timeo_p = schedule_timeout(*timeo_p);//3
}            

1.创建一个wait队列,注册回调,并把当前进程current挂到private成员上。

#define DEFINE_WAIT_FUNC(name, function)                    \
    struct wait_queue_entry name = {                    \
        .private    = current,                  \
        .func       = function,                 \
        .entry      = LIST_HEAD_INIT((name).entry),         \
    }

sk_sleep(sk)返回sk->sk_wq->wait,即sk的等待队列的wait成员(队列头)。

struct socket_wq {
    /* Note: wait MUST be first field of socket_wq */
	wait_queue_head_t   wait;} 

然后将新创建的wait挂到wait_queue_head_t 上,当数据就绪时,内核从sk的sk_wait上取下线程的current指针,唤醒线程。

bool
prepare_to_wait_exclusive(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state)
{
    unsigned long flags;
    bool was_empty = false;

    wq_entry->flags |= WQ_FLAG_EXCLUSIVE;
    spin_lock_irqsave(&wq_head->lock, flags);
    if (list_empty(&wq_entry->entry)) {
        was_empty = list_empty(&wq_head->head);
        __add_wait_queue_entry_tail(wq_head, wq_entry);
    }
    set_current_state(state);
    spin_unlock_irqrestore(&wq_head->lock, flags);
    return was_empty;
}

最后调用*timeo_p = schedule_timeout(*timeo_p)切换线程。
整个流程如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值