当前位置:首页>Linux>Linux 网络系统:CAN 帧从应用层到硬件到底经历了什么

Linux 网络系统:CAN 帧从应用层到硬件到底经历了什么

  • 2026-06-28 00:21:16
Linux 网络系统:CAN 帧从应用层到硬件到底经历了什么

Linux 网络系统:CAN 帧从应用层到硬件到底经历了什么

设想你在 Linux 上用 C 写了一段程序,调用 write(fd, &frame, sizeof(frame)) 发送一条 CAN 帧。这行代码看起来跟写普通文件没什么区别——但在它背后,数据穿越了 glibc、系统调用、VFS、Socket 层、CAN 协议栈、网络设备子系统,最后抵达 CAN 控制器的硬件寄存器,整个路程跨越了七八个内核子系统。

本文以 Linux 4.9 主线内核 为基准,沿着一条最普通的 SocketCAN write() 发送路径,把每一层做了什么、数据长什么样、下一站去哪,一步步拆开来看。文中的源码路径和函数名均按 v4.9 口径描述;较新内核里部分函数签名或辅助接口可能已经演进,但主干分层思想仍然一致。

本文聚焦 Classical CAN + CAN_RAW socket + write() 发送路径。CAN FD、BCM、netlink 配置、接收过滤和错误帧不是主线,但在容易混淆的地方会做必要标注。ISO-TP 在较新内核和一些发行版/外部模块中很常见,但它不是 Linux 4.9 主线 CAN 协议栈的重点对象,本文不展开。

先给出一条总览链路:

应用 write()
  -> glibc / syscall
  -> fs/read_write.c: vfs_write()
  -> net/socket.c: sock_write_iter()
  -> net/socket.c: sock_sendmsg()
  -> net/can/raw.c: raw_sendmsg()
  -> net/can/af_can.c: can_send()
  -> net/core/dev.c: dev_queue_xmit()
  -> dev->netdev_ops->ndo_start_xmit()
  -> CAN 控制器驱动
  -> CAN 控制器寄存器 / CAN 收发器 / CAN 总线

理解这条链路的关键,不是背函数名,而是看清三层"分发":每一层都在回答"接下来该谁处理"。

  1. 1. 协议族分发PF_CAN 让内核在 net_families[] 中定位到 CAN 协议族,调用 can_create() 创建 socket。
  2. 2. 协议操作分发CAN_RAW + SOCK_RAW 让 socket 绑定 raw_ops,后续的 sendmsg/bind 等操作自然落到 CAN RAW 的处理函数。
  3. 3. 设备驱动分发raw_sendmsg 根据 bind() 保存的 ifindex 找到 can0 的 net_device,赋值 skb->dev = dev。之后网络设备层通过 dev->netdev_ops->ndo_start_xmit 调到具体 CAN 控制器的驱动函数。

三层分发串起来,就是一条完整的"从通用到专用"的调用链。

CAN 应用层使用示例

在深入内核之前,先站在用户态看一眼:下面这个程序就是我们要追踪的起点。它用 SocketCAN 标准接口创建 CAN_RAW socket、绑定 can0、发送一条 8 字节 Classical CAN 帧。内核里几百行的调用链,起点就是这几行代码。

运行前需要先把 CAN 设备配置拉起。真实设备通常需要 CAP_NET_ADMIN 权限;如果设备已经 UP,多数驱动要求先 down 再改 bit timing。

sudo ip link set can0 down
sudo
 ip link set can0 type can bitrate 500000
sudo
 ip link set can0 up

如果你只是本机验证 SocketCAN 调用链,可以用 vcan 虚拟设备替代真实 CAN 控制器;但 vcan 不会走具体硬件驱动的寄存器发送路径。

#include <stdio.h>
#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <errno.h>

#include <sys/ioctl.h>

#include <net/if.h>

#include <sys/socket.h>

#include <linux/can.h>

#include <linux/can/raw.h>


#define CAN_INTERFACE "can0"


int
 main(void) {
    int
 s;
struct sockaddr_can addr;

struct ifreq ifr;

struct can_frame frame;

    ssize_t
 nbytes;

    memset
(&addr, 0, sizeof(addr));
    memset
(&ifr, 0, sizeof(ifr));
    memset
(&frame, 0, sizeof(frame));

    // 创建 CAN RAW socket:PF_CAN 选择 CAN 协议族,CAN_RAW 选择 RAW 协议。

    s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
    if
 (s < 0) {
        perror("socket");
        return
 1;
    }

    // 将 "can0" 转换为内核使用的 ifindex。bind 需要数字索引,而不是接口名字符串。

    strncpy
(ifr.ifr_name, CAN_INTERFACE, IFNAMSIZ - 1);
    if
 (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
        perror("ioctl(SIOCGIFINDEX)");
        close(s);
        return
 1;
    }

    // 绑定到 can0 后续 write() 不需要再指定发送网卡。

    addr.can_family = AF_CAN;
    addr.can_ifindex = ifr.ifr_ifindex;
    if
 (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(s);
        return
 1;
    }

    // Classical CAN 帧:标准帧 ID 0x123,DLC = 8。

    frame.can_id = 0x123;
    frame.can_dlc = 8;
    for
 (int i = 0; i < frame.can_dlc; i++) {
        frame.data[i] = i;
    }

    nbytes = write(s, &frame, sizeof(frame));
    if
 (nbytes < 0) {
        perror("write");
        close(s);
        return
 1;
    }
    if
 (nbytes != sizeof(frame)) {
        fprintf
(stderr, "write: incomplete CAN frame\n");
        close(s);
        return
 1;
    }
    printf
("CAN frame sent on %s\n", CAN_INTERFACE);

    close(s);
    return
 0;
}

这里只演示发送,没有在 write() 后立刻 read()。注意一个容易踩的坑:CAN RAW socket 默认开启本地回环(其他监听同一网卡的 socket 能收到你发的帧),但默认不会把自己发送的帧投递回发送者自身。如果你想在同一个 socket 上发完立刻读回来,需要显式设置 setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &on, sizeof(on)),否则 read() 会一直阻塞——除非总线上有其他节点发来了帧。

socket 从应用到底层

应用调用 socket(PF_CAN, SOCK_RAW, CAN_RAW) 触发 syscall 后,内核用三个参数依次完成:协议族查找 → 传输协议匹配 → 操作表绑定 → fd 分配。这一步做完,用户态拿到了一个普通的 fd,内核里则生成了两个关键对象:

  1. 1. struct file:VFS 层视角下的"文件",让 write/read/ioctl 等系统调用能通过 fd 找到 socket。
  2. 2. struct socket + struct sock:网络栈视角下的"套接字",协议操作表已绑定为 CAN RAW 的 raw_ops
// https://github1s.com/torvalds/linux/blob/v4.9/net/socket.c#L1218
// socket 系统调用入口

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
    // ...

    retval = sock_create(family, type, protocol, &sock);  // 创建 socket 对象
    // ...

    retval = sock_map_fd(sock, flags);  // 绑定 socket_file_ops,分配 fd 返回应用
    // ...

}

三个参数各做什么

参数
内核作用
familyPF_CAN
在 net_families[] 中查找 CAN 协议族,调用其 createcan_create
typeSOCK_RAW
记录 socket 类型;与候选协议的 type 必须一致,否则 -EPROTOTYPE
protocolCAN_RAW
在 CAN 协议表 proto_tab[] 中查找 RAW 协议(值为 1),取得对应 can_proto

PF_CAN 和 AF_CAN 在用户态代码中经常混用。严格来说,PF_* 表示 protocol family(创建 socket 时使用),AF_* 表示 address family(绑定地址时使用);在 Linux 里 CAN 的这两个值相同(都是 29),所以 socket(PF_CAN, ...) 和 addr.can_family = AF_CAN 搭配使用不会出错。

查找与绑定流程

内核按参数分四步完成查找与绑定:

第 1 步:PF_CAN 选定协议族

内核维护一张协议族表 net_families[],各协议族模块启动时注册自己的 create 函数;socket(PF_CAN, ...) 时据此找到 CAN 并调用 can_create

// https://github1s.com/torvalds/linux/blob/v4.9/net/socket.c#L163 — 协议族表,下标为 family 值
static
 conststruct net_proto_family __rcu *net_families[NPROTO];

// https://github1s.com/torvalds/linux/blob/v4.9/net/can/af_can.c#L886-L890 — CAN 模块初始化时注册,填入 net_families[PF_CAN]

static
 conststruct net_proto_family can_family_ops = {
    .family = PF_CAN,
    .create = can_create,       // 创建 CAN socket 的入口函数
};
// https://github1s.com/torvalds/linux/blob/v4.9/net/can/af_can.c#L924

sock_register(&can_family_ops);

// https://github1s.com/torvalds/linux/blob/v4.9/net/socket.c#L1206 — socket 创建核心逻辑

int
 sock_create(int family, int type, int protocol, struct socket **res)
{
    return
 __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}

// https://github1s.com/torvalds/linux/blob/v4.9/net/socket.c#L1097-L1098

int
 __sock_create(struct net *net, int family, int type, int protocol,
                  struct
 socket **res, int kern)
{
    // ...

    sock->type = type;                                          // 记录 SOCK_RAW
    pf = rcu_dereference(net_families[family]);                 // PF_CAN → can_family_ops
    // ...

    err = pf->create(net, sock, protocol, kern);                // 调用 can_create()
    // ...

}

第 2 步:CAN_RAW 选定传输协议

can_create 根据 protocol 查 CAN 协议表,取得 raw_can_proto,其中携带 raw_ops 与 raw_proto

// https://github1s.com/torvalds/linux/blob/v4.9/net/can/af_can.c#L136-L137
static
 int can_create(struct net *net, struct socket *sock, int protocol, ...)
{
    // ...

    cp = can_get_proto(protocol);   // 查 proto_tab[CAN_RAW] → raw_can_proto
    // ...

}

// https://github1s.com/torvalds/linux/blob/v4.9/net/can/af_can.c#L118

// 按 protocol 下标查 proto_tab[]

static
 const struct can_proto *can_get_proto(int protocol)
{
    // ...

    cp = rcu_dereference(proto_tab[protocol]);
    // ...

}

// https://github1s.com/torvalds/linux/blob/v4.9/net/can/raw.c#L853-L858

// raw 模块加载时注册到 proto_tab[CAN_RAW]

static
 conststruct can_proto raw_can_proto = {
    .type = SOCK_RAW,
    .protocol = CAN_RAW,
    .ops = &raw_ops,        // socket 层操作表
    .prot = &raw_proto,     // sock 内存分配与初始化
};

// https://github1s.com/torvalds/linux/blob/v4.9/net/can/raw.c#L866

can_proto_register(&raw_can_proto);

第 3 步:SOCK_RAW 校验类型并绑定 socket

确认协议类型与 socket 类型匹配后,绑定 sock->ops 并分配 raw_sock

// https://github1s.com/torvalds/linux/blob/v4.9/net/can/af_can.c#L136-L137
static
 int can_create(struct net *net, struct socket *sock, int protocol, ...)
{
    // ...

    if
 (cp->type != sock->type)     // raw_can_proto.type 须等于 SOCK_RAW
        return
 -EPROTOTYPE;

    sock->ops = cp->ops;            // → raw_ops(sendmsg/bind 等)
    sk = sk_alloc(net, PF_CAN, GFP_KERNEL, cp->prot, kern);  // 分配 raw_sock
    // ...

}

第 4 步:分配 fd,绑定 VFS 操作表

为 socket 创建 file 对象,挂上通用的 socket_file_ops,应用拿到的 fd 即指向该 file。

为什么所有 socket 用的都是 socket_file_ops?这不是运行时查出来的,而是 sock_alloc_file 在创建 file 时硬编码传入了 alloc_file(..., &socket_file_ops)。所有 socket(TCP、UDP、CAN……)在 VFS 层共用这一套 file 操作表,协议差异由下层 sock->ops(如 CAN RAW 的 raw_ops)承担。调用链是这样的:

  1. 1. 应用 write(fd) → VFS 调 file->f_op->write_iter(即 sock_write_iter
  2. 2. sock_write_iter 通过 file->private_data 找到 struct socket
  3. 3. 转调 sock->ops->sendmsg(即 raw_sendmsg

这样一来,VFS 层只需要知道"这是个 socket",完全不需要关心它是什么协议。

// https://github1s.com/torvalds/linux/blob/v4.9/net/socket.c#L433
static
 int sock_map_fd(struct socket *sock, int flags)
{
    // ...

    newfile = sock_alloc_file(sock, flags, NULL);
    fd_install(fd, newfile);                       // fd 与 file 关联,返回给应用
    // ...

}

// https://github1s.com/torvalds/linux/blob/v4.9/net/socket.c#L397

struct
 file *sock_alloc_file(struct socket *sock, int flags, const char *dname)
{
    // ...

    file = alloc_file(&path, FMODE_READ | FMODE_WRITE, &socket_file_ops);  // 此处绑定
    file->private_data = sock;   // file 与 socket 关联,write_iter 中据此取 sock
    // ...

}

绑定了哪些 ops

socket 创建后涉及两层操作表,职责不同:

1. VFS 层 file->f_op = socket_file_ops(所有 socket 共用)

由 sock_alloc_file 设置,负责 read/write/ioctl 等文件接口。其中 write_iter = sock_write_iter,后续 write(fd, ...) 经此进入 socket 发送路径。

2. Socket 层 sock->ops = raw_ops(由 CAN_RAW + SOCK_RAW 选定)

由 can_create 从 raw_can_proto 绑定,负责 socket 语义操作。与 CAN 收发相关的主要成员:

成员
函数
作用
sendmsgraw_sendmsg
发送 CAN 帧(write 最终落到此)
recvmsgraw_recvmsg
接收 CAN 帧
bindraw_bind
绑定到 can0 等网络设备
setsockoptraw_setsockopt
过滤器、回环等 RAW 选项
// net/can/af_can.c — can_create 绑定 sock->ops
static
 int can_create(struct net *net, struct socket *sock, int protocol, ...)
{
    // ...

    cp = can_get_proto(protocol);   // CAN_RAW → raw_can_proto
    sock->ops = cp->ops;            // → raw_ops
    sk = sk_alloc(net, PF_CAN, GFP_KERNEL, cp->prot, kern);
    // ...

}

// net/can/raw.c — CAN RAW 协议的操作表,write 最终经 sendmsg 落到 raw_sendmsg

static
 conststruct proto_ops raw_ops = {
    // ...

    .bind = raw_bind,
    .sendmsg = raw_sendmsg,
    .recvmsg = raw_recvmsg,
    // ...

};

static
 conststruct can_proto raw_can_proto = {
    .type = SOCK_RAW,
    .protocol = CAN_RAW,
    .ops = &raw_ops,
    .prot = &raw_proto,
};

因此:PF_CAN 决定走 CAN 协议族,CAN_RAW + SOCK_RAW 决定 sock->ops 为 raw_opssock_map_fd 再为 fd 挂上通用的 socket_file_ops。后续 write 先经 VFS 的 socket_file_ops,再经 sock->ops->sendmsg(即 raw_sendmsg)完成 CAN 发送。

write 从应用到底层

syscall 入口

socket() 创建好了 fd,现在应用调用 write(fd, &frame, sizeof(frame))。glibc 将其封装为 syscall(SYS_write, fd, buf, count),CPU 执行 syscall 指令陷入内核。

内核根据 fd 查进程的文件描述符表,拿到 struct file(fd 无效则直接返回 -EBADF)。这个 file 在 socket() 时已经绑定了 f_op = &socket_file_ops,所以后续 vfs_write → __vfs_write 会按 socket 路径继续分发,而不是走普通文件的写逻辑。

// https://github1s.com/torvalds/linux/blob/v4.9/fs/read_write.c#L599-L600
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
        size_t
, count)
{
    // ...

    ret = vfs_write(f.file, buf, count, &pos);  // f.file 即 socket() 时创建的 file
    // ...

}

VFS 层

vfs_write 是 VFS 层的通用写入口。它做了三件事:校验文件是否可写(FMODE_WRITE)、检查用户态缓冲区是否可读(access_ok)、通过 rw_verify_area 检查写入范围是否合法(对 socket 而言,这个检查基本是空操作)。通过校验后,将 count 截断至 MAX_RW_COUNT,然后调用 __vfs_write

// https://github1s.com/torvalds/linux/blob/v4.9/fs/read_write.c#L544
// VFS 通用写入口:权限与缓冲区校验后转 __vfs_write

ssize_t
 vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
    // ... 校验 FMODE_WRITE、access_ok、rw_verify_area ...

    if
 (count > MAX_RW_COUNT)
        count = MAX_RW_COUNT;           // 限制单次写入上限
    // ...

    ret = __vfs_write(file, buf, count, pos);
    // ...

}

__vfs_write 通过 file->f_op 分发写操作。对 socket 的 fd 而言,这个 f_op 就是 socket_file_ops(在 socket() 创建时由 sock_alloc_file 绑定);其中未实现 write,只实现了 write_iter,因此走 new_sync_write → sock_write_iter

// https://github1s.com/torvalds/linux/blob/v4.9/fs/read_write.c#L506-L507
// 按 file->f_op 分发;socket 无 write,走 write_iter 分支

ssize_t
 __vfs_write(struct file *file, const char __user *p, size_t count,
            loff_t
 *pos)
{
    if
 (file->f_op->write)
        return
 file->f_op->write(file, p, count, pos);
    else
 if (file->f_op->write_iter)
        return
 new_sync_write(file, p, count, pos);  // socket 走此分支
    else

        return
 -EINVAL;
}

// https://github1s.com/torvalds/linux/blob/v4.9/net/socket.c#L140

// socket 的 file 操作表,仅实现 write_iter

static
 conststruct file_operations socket_file_ops = {
    // ...

    .write_iter = sock_write_iter,
    // ...

};

socket_file_ops 只实现了 write_iter,没实现传统的 write。因此 __vfs_write 不能直接写 socket,需要 new_sync_write 做一层中转。

为什么需要这层中转? 应用 write 提供的是"一块内存 + 长度",而 write_iter 接口需要的是"写请求描述"(kiocb + iov_iter)。new_sync_write 负责把前者适配为后者——注意这里只是接口适配,并不是把数据拷进内核(真正的拷贝发生在后面的 raw_sendmsg)。网络栈统一走 write_iter,是为了兼容 writev、AIO 等路径,不必同时维护两套写逻辑。

// https://github1s.com/torvalds/linux/blob/v4.9/fs/read_write.c#L488
// 同步写适配:把用户 buf 包装后调用 write_iter

static
 ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
    // ... 将 buf 登记为 iovec,初始化 kiocb、iov_iter ...

    ret = filp->f_op->write_iter(&kiocb, &iter);  // → sock_write_iter
    // ...

}

socket 层

进入 socket 层,sock_write_iter 做三件事:

  1. 1. 从 file->private_data 取出 struct socket——这是 socket() 时关联的。
  2. 2. 把用户数据和控制信息组装成 struct msghdr
  3. 3. 处理边界条件:socket 不支持带偏移写入(偏移非 0 返回 -ESPIPE),非阻塞 fd 附加 MSG_DONTWAIT 标志。

然后调用 sock_sendmsg,进入协议发送路径。

// https://github1s.com/torvalds/linux/blob/v4.9/net/socket.c#L812
// 从 file 取 socket,组装 msghdr 后进入发送路径

static
 ssize_t sock_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct file *file =
 iocb->ki_filp;
struct socket *sock =
 file->private_data;       // socket() 时关联的 socket
struct msghdr msg =
 {.msg_iter = *from,         // 待写的用户数据
                         .msg_iocb = iocb};
    // ... 偏移校验、非阻塞标志处理 ...

    res = sock_sendmsg(sock, &msg);                 // socket 发送统一入口
    // ...

}

sock_write_iter 最终调用 sock_sendmsg,这是 socket 发送的统一入口:先做安全模块校验,通过后经 sock_sendmsg_nosec 调用 sock->ops->sendmsg;对 CAN RAW socket,即 raw_sendmsg

// https://github1s.com/torvalds/linux/blob/v4.9/net/socket.c#L626
// 发送统一入口:安全校验 → 协议 sendmsg

int
 sock_sendmsg(struct socket *sock, struct msghdr *msg)
{
    // ... security_socket_sendmsg 安全校验 ...

    return
 err ?: sock_sendmsg_nosec(sock, msg);
}

static
 inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg)
{
    // sock->ops 在 socket() 时绑定为 raw_ops,sendmsg 即 raw_sendmsg

    int
 ret = sock->ops->sendmsg(sock, msg, msg_data_left(msg));
    // ...

    return
 ret;
}

RAW 协议层

sock_sendmsg_nosec 通过 sock->ops->sendmsg 跳出通用 socket 层,进入 raw_sendmsg——这是在 socket() 创建时由 raw_ops 绑定的。raw_sendmsg 是 CAN RAW 发送的核心,负责确定出口网卡、检查帧长度、分配内核 skb、把用户态数据拷入内核,并把发送者 socket 与出口设备记录到 skb 上。

// https://github1s.com/torvalds/linux/blob/v4.9/net/can/raw.c#L714
// CAN RAW 协议发送入口

static
 int raw_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
    // write() 路径没有 msg_name,因此使用 bind() 时保存的 ifindex

    // sendto()/sendmsg() 路径也可以通过 sockaddr_can 临时指定 ifindex

    // ... 根据 ifindex 经 dev_get_by_index 找到 can0 等 net_device ...

    // ... Classical CAN 默认要求 size == CAN_MTU ...

    skb = sock_alloc_send_skb(sk, size + sizeof(struct can_skb_priv), ...);
    // ...

    can_skb_prv(skb)->ifindex = dev->ifindex;
    err = memcpy_from_msg(skb_put(skb, size), msg, size);  // 用户 can_frame → skb
    // ...

    skb->dev = dev;                            // 指定出口网卡,供 __dev_queue_xmit 使用
    skb->sk = sk;                              // 记录发送者,回环时用于识别 own message
    err = can_send(skb, ro->loopback);
    // ...

}

对 Classical CAN 发送来说,write() 的长度必须是 sizeof(struct can_frame),也就是 CAN_MTU(16 字节)。如果 socket 没有绑定具体网卡,又没有在 sendto()/sendmsg() 里通过 sockaddr_can 指定 can_ifindexraw_sendmsg 找不到出口设备,会返回错误;bind() 到 ifindex 0 可以用于接收所有 CAN 设备上的帧,但不能让一次普通 write() 自动选择发送网卡。

如果是 CAN FD,应用需要先过 setsockopt(CAN_RAW_FD_FRAMES) 打开 CAN FD 支持。开启后,CAN RAW socket 可以接收/发送 CAN_MTU 或 CANFD_MTU 两种长度;未开启时只接受 CAN_MTU。设备本身还必须具备对应的 CAN FD 能力和 MTU 配置,否则后续 can_send 或驱动层仍可能拒绝发送。本文不展开 CAN FD 路径。

AF_CAN 核心层

can_send 是 AF_CAN 协议族的公共发送路径。在 v4.9 主线里,RAW、BCM 等 CAN 协议模块发送 skb 时会走到这里;其他内核版本或外部协议模块也通常复用这个出口。它负责校验 CAN 帧的有效性、处理本地回环,然后调用 dev_queue_xmit 将 skb 投入网络设备发送队列。

// https://github1s.com/torvalds/linux/blob/v4.9/net/can/af_can.c#L227
// AF_CAN 共用发送路径

int
 can_send(struct sk_buff *skb, int loop)
{
    // ... 校验 CAN_MTU/CANFD_MTU、DLC/len、网卡 MTU、ARPHRD_CAN、IFF_UP ...

    // ... 本地回环处理:设置 skb->pkt_type,必要时克隆 skb ...

    err = dev_queue_xmit(skb);   // 调用 dev_queue_xmit 进入网络设备子系统
    // ...

}

这里的"本地回环"容易被误解。它不是 CAN 控制器从 CANH/CANL 物理线上把电平读回来,也不是 CAN 总线 ACK 机制本身;它是 SocketCAN 为本机其他监听同一网卡的 socket 提供的一份本地副本,用来满足多进程监听同一 CAN ID 的场景。具体机制分两种情况:

  • • 如果驱动声明了 IFF_ECHOcan_send 会把 skb 标记为 PACKET_LOOPBACK,由驱动保存 echo skb,并通常在发送完成中断里通过 can_get_echo_skb() 完成回环/释放。
  • • 如果驱动不支持 IFF_ECHOcan_send 会克隆一份 skb;原始 skb 继续走硬件发送,克隆 skb 在发送成功后由 CAN core 投递回本机接收路径。

简单说:回环是给本机其他 socket 看的,不是给发送者自己看的(除非你显式开启了 CAN_RAW_RECV_OWN_MSGS)。

网络设备层

can_send 通过 dev_queue_xmit 进入网络设备子系统(net/core/dev.c)。dev_queue_xmit 只是一个薄封装,实际逻辑在 __dev_queue_xmit

  1. 1. 根据 skb->dev 定位出口网卡。
  2. 2. 选取发送队列,经过 qdisc(排队规则)调度。
  3. 3. 调用 dev_hard_start_xmit 发起实际发送。

其中 skb->dev 的值来自上一步 raw_sendmsg:应用 bind(s, can0) 后,raw_sendmsg 用绑定的 ifindex 找到 can0 的 net_device,执行 skb->dev = dev

// https://github1s.com/torvalds/linux/blob/v4.9/net/core/dev.c#L3415
// 网卡发送入口:dev_queue_xmit → __dev_queue_xmit

int
 dev_queue_xmit(struct sk_buff *skb)
{
    return
 __dev_queue_xmit(skb, NULL);
}

static
 int __dev_queue_xmit(struct sk_buff *skb, ...)
{
    dev = skb->dev;                          // raw_sendmsg 中 skb->dev = dev(bind 的 can0)
    txq = netdev_pick_tx(dev, skb, ...);
    q = rcu_dereference_bh(txq->qdisc);
    // ... skb 经 qdisc 入队、调度 ...

    skb = dev_hard_start_xmit(skb, dev, txq, &rc);
    // ...

}

dev_hard_start_xmit 遍历待发送的 skb 链表,对每个 skb 调用 xmit_one 完成一次发送。

// https://github1s.com/torvalds/linux/blob/v4.9/net/core/dev.c#L2920-L2921
// 逐包调用 xmit_one

struct
 sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *dev,
                                    struct
 netdev_queue *txq, int *ret)

{
    // ...

    rc = xmit_one(skb, dev, txq, next != NULL);
    // ...

}

xmit_one 对单个 skb 调用 netdev_start_xmit,进入驱动回调链。

// https://github1s.com/torvalds/linux/blob/v4.9/net/core/dev.c#L2903-L2904
// 单包发送,转 netdev_start_xmit

static
 int xmit_one(struct sk_buff *skb, struct net_device *dev, ...)
{
    // ...

    rc = netdev_start_xmit(skb, dev, txq, more);
    // ...

}

netdev_start_xmit 从 dev->netdev_ops 取出操作表,调用 __netdev_start_xmit,最终执行驱动注册的 ndo_start_xmit。此处并非写死 flexcan_start_xmit,而是取决于 skb->dev 对应哪块网卡、该网卡由哪个驱动创建——以 FlexCAN 为例,驱动 probe 时绑定 dev->netdev_ops = &flexcan_netdev_ops,其中 .ndo_start_xmit = flexcan_start_xmit

// https://github1s.com/torvalds/linux/blob/v4.9/include/linux/netdevice.h#L4050-L4051
// 取 dev->netdev_ops,转 ndo_start_xmit(具体函数由驱动 probe 时绑定)

static
 inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,
                                            struct
 netdev_queue *txq, bool more)

{
    // ...

    rc = __netdev_start_xmit(ops, skb, dev, more);
    // ...

}

// https://github1s.com/torvalds/linux/blob/v4.9/include/linux/netdevice.h#L4042-L4044

static
 inline netdev_tx_t __netdev_start_xmit(const struct net_device_ops *ops,
                                              struct
 sk_buff *skb, struct net_device *dev,
                                              bool
 more)
{
    skb->xmit_more = more ? 1 : 0;
    return
 ops->ndo_start_xmit(skb, dev);   // dev 为 can0 时 → 对应驱动的 start_xmit
}

CAN 设备驱动层

ndo_start_xmit 由具体 CAN 控制器驱动实现(在 drivers/net/can/ 下,如 flexcan.cmcp251x.c 等)。这个绑定发生在驱动加载 probe 时,与 socket() / write() 调用无关:

  1. 1. alloc_candev 分配 can0 的 net_device 和驱动私有数据
  2. 2. dev->netdev_ops = &flexcan_netdev_ops——挂上该驱动的操作表
  3. 3. flexcan_netdev_ops.ndo_start_xmit = flexcan_start_xmit——指定发送函数
  4. 4. register_candev(dev) 将网卡注册进内核,can0 正式可用

之后每次发送,raw_sendmsg 通过 bind 保存的 ifindex 找到这个 net_device,设置 skb->dev = dev。网络设备层的 ops->ndo_start_xmit 自然落到该驱动的发送函数。换用 mcp251x 芯片时,这里就是 mcp251x_start_xmit,替换方式完全一样。

以 FlexCAN 为例:驱动从 skb 取出 can_frame,根据标准帧/扩展帧、RTR 标志和 DLC 组织控制字段,把 ID 与数据写入 TX mailbox 寄存器,最后写控制寄存器触发 CAN 控制器发送。控制器再通过 TX/RX 引脚连接 CAN 收发器,由收发器驱动 CANH/CANL 差分总线;很多 SoC 数据手册会把控制器和外部收发器分开描述,调试硬件问题时不要把两者混为一谈。

// drivers/net/can/flexcan.c — probe 时绑定 netdev_ops
dev = alloc_candev(sizeof(struct flexcan_priv), 1);
dev->netdev_ops = &flexcan_netdev_ops;
register_candev(dev);   // 注册 can0

// https://github1s.com/torvalds/linux/blob/v4.9/drivers/net/can/flexcan.c#L1067

static
 conststruct net_device_ops flexcan_netdev_ops = {
    .ndo_start_xmit = flexcan_start_xmit,
    // ...

};

// https://github1s.com/torvalds/linux/blob/v4.9/drivers/net/can/flexcan.c#L468

static
 int flexcan_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct can_frame *cf =
 (struct can_frame *)skb->data;
    // ... netif_stop_queue(dev) ...

    // ... can_put_echo_skb(skb, dev, 0) ...

    // ... 写 TX mailbox 的 ID、DATA、CTRL 寄存器,触发硬件发送 ...

    return
 NETDEV_TX_OK;
}

发送完成后,FlexCAN 这类驱动通常在 TX complete 中断里更新统计信息,调用 can_get_echo_skb 取回并释放 echo skb,再通过 netif_wake_queue 恢复发送队列。不同 CAN 控制器的寄存器细节不同,但对网络设备子系统暴露出来的关键边界仍然是 ndo_start_xmit

完整链路(应用 → 物理总线)

下面用流程图把整条链路串起来,每一层对应内核的一个子系统。从用户态的 write(),到硬件寄存器,数据就是这样一层层传下去的。

用户态

应用里的 write() 并非直接进内核,而是先经 glibc(或 musl 等 C 库)封装,由库函数准备参数并执行 syscall 指令,再陷入内核。

glibc · 用户态
应用 · 用户程序
write(fd, buf, len)
__libc_write / __write
syscall(SYS_write, ...)

↓ 进入内核

内核 · 通用 I/O 与 Socket

syscall / VFS 层 · fs/read_write.c

fs/read_write.c
SYSCALL_DEFINE3(write)
vfs_write
__vfs_write
new_sync_write

socket 层 · net/socket.c

socket 层
sock_write_iter
sock_sendmsg
sock->ops->sendmsg

内核 · CAN 协议栈

CAN RAW 层 · net/can/raw.c

CAN RAW 层
raw_sendmsg

AF_CAN 层 · net/can/af_can.c

AF_CAN 层
can_send
dev_queue_xmit

内核 · 网络设备子系统

网络设备层 · net/core/dev.c

网络设备层
dev_queue_xmit
__dev_queue_xmit
dev_hard_start_xmit
xmit_one
netdev_start_xmit
ndo_start_xmit

驱动与硬件

驱动层 · drivers/net/can/*.c

驱动层
flexcan_start_xmit

硬件 · CAN 控制器与收发器

硬件
写寄存器
CAN 控制器
CAN 收发器
CANH/CANL 总线

层级
关键文件
关键函数
应用
用户程序
write()
glibc
unistd/write.c
 等
__libc_write
 → syscall(SYS_write)
内核 syscall
fs/read_write.cSYSCALL_DEFINE3(write)
 → vfs_write
VFS
fs/read_write.cvfs_write
 → new_sync_write
socket
net/socket.csock_write_iter
 → sock_sendmsg
CAN RAW
net/can/raw.craw_sendmsg
AF_CAN
net/can/af_can.ccan_send
网络设备
net/core/dev.cdev_queue_xmit
 → netdev_start_xmit
驱动
drivers/net/can/*.cndo_start_xmit
(如 flexcan_start_xmit
硬件
CAN 控制器 + CAN 收发器
寄存器 → 控制器 TX/RX → 收发器 → CANH/CANL 总线

总结

表面上看,发送 CAN 帧就是一行 write(fd, &frame, sizeof(frame))。但在内核里,这行代码触发了七八层子系统的协作。回看一遍完整的数据流:

用户态:  write(fd, &frame, sizeof(frame))
   ↓
glibc:   syscall(SYS_write, fd, buf, count)
   ↓
VFS:     vfs_write → __vfs_write → new_sync_write(文件→socket 接口适配)
   ↓
Socket:  sock_write_iter → sock_sendmsg → sock->ops->sendmsg
   ↓
CAN RAW: raw_sendmsg(确定出口网卡、分配 skb、拷贝数据)
   ↓
AF_CAN:  can_send(校验帧、处理回环)
   ↓
Netdev:  dev_queue_xmit → __dev_queue_xmit → dev_hard_start_xmit → ndo_start_xmit
   ↓
驱动:    flexcan_start_xmit / mcp251x_start_xmit(写寄存器)
   ↓
硬件:    CAN 控制器 → CAN 收发器 → CANH/CANL 总线

SocketCAN 设计最值得学习的一点是:CAN 控制器虽然是嵌入式外设,但 Linux 没有把它做成字符设备,而是纳入了网络设备模型。这个设计决策带来了三层收益:

  • • 应用层:用标准 socket API,write/read/select/poll/epoll 全部可用。
  • • 协议层:复用网络栈的分发、队列和过滤机制,不用重新发明轮子。
  • • 驱动层:只需实现 net_device_ops 中的硬件相关操作,其余由内核框架提供。

读这条路径时,建议抓住四个核心对象,它们贯穿了从应用到硬件的全过程:

对象
作用
出现时机
struct file
让 fd 走 VFS 通用路径
socket()
 创建时
struct socket
 / struct sock
承载 socket 语义和协议私有状态
socket()
 创建时
struct sk_buff
在内核各层间流动的数据载体
write()
 发送时
struct net_device
代表 can0,把发送动作分发到具体驱动
驱动 probe 时

把这四个对象串起来,write() 到 CAN 总线的路径就不再是一串零散的函数名,而是一条清晰的分层调用链。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-02 23:26:28 HTTP/2.0 GET : https://f.mffb.com.cn/a/500258.html
  2. 运行时间 : 0.688202s [ 吞吐率:1.45req/s ] 内存消耗:4,650.13kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=75db5f4af5344fac8544c84f5572740a
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000406s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000595s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.003013s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.007881s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000672s ]
  6. SELECT * FROM `set` [ RunTime:0.000313s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000651s ]
  8. SELECT * FROM `article` WHERE `id` = 500258 LIMIT 1 [ RunTime:0.027990s ]
  9. UPDATE `article` SET `lasttime` = 1783005988 WHERE `id` = 500258 [ RunTime:0.012303s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.011596s ]
  11. SELECT * FROM `article` WHERE `id` < 500258 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.009238s ]
  12. SELECT * FROM `article` WHERE `id` > 500258 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.007209s ]
  13. SELECT * FROM `article` WHERE `id` < 500258 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.119642s ]
  14. SELECT * FROM `article` WHERE `id` < 500258 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.388974s ]
  15. SELECT * FROM `article` WHERE `id` < 500258 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.019878s ]
0.690852s