基于netlink的Linux Network Monitor实现

2023-05-16

一、背景

来源于产品开发需求,需要在linux系统下实现网络状类型查询及网络类型变更通知,比如从Ethernet变为Wifi,从Wifi变为Ethernet等。

二、设计方案

  • Linux系统提供了Netlink套接字用以实现用户进程与内核进程通信的一种特殊的进程间通信,本文实现的获取客户端网络类型便采用Netlink进行实现。
  • 在Linux桌面端系统下网络类型一般有以下两种:
    • 有线网:Ethernet
    • 无线网:Wifi
  • 模块间通信框图如下:
  • 在这里插入图片描述

三、实现流程图

实现步骤:

  1. 获取处于在线状态(running)的网卡Link列表
  2. 排除以下内部或特殊网卡:
    • 本地环回网络(loopback,127.0.0.1)
    • 虚拟机网络(vmnet)
    • tunnel网络(tun*)
    • docker网络(docker
  3. 针对最终的目标Link列表遍历对应网卡的网络类型(使用ioctl系统调用),如果一致则变更网络类型(Ethernet或Wifi),否则变更网络类型(Unknown)
  4. 若不存在在线网卡,则变更网络类型(None)
网络类型
None无在线网卡
Ethernet在线网卡类型一致,均为Ethernet
Wifi在线网卡类型一致,均为Wifi
Unknown在线网卡类型不一致,比如同时存在Ethernet和Wifi

在这里插入图片描述

四、后续优化计划

  1. 采用netlink方案可以实现ipv4和ipv6变更的通知;
  2. Linux系统发行版比较多和场景碎片化,目前主要考虑桌面端和服务器系统下的表现,针对IOT设备暂时未详细验证,后续针对IOT设备可进一步验证和优化。

五、技术栈

  • netlink
    • 用于获取在线网卡网络类型
  • epoll
    • 用于实现多路复用IO
  • eventfd
    • 用于实现线程(event loop)唤醒

六、源码参考

  • network_monitor.h
#pragma once

#include <thread>
#include <atomic>
#include <mutex>
#include <set>
#include <sys/epoll.h>

enum NetworkType : int32_t {
  kNetworkUnknown = 0,  // A connection exists, but its type is unknown. Also used as a default value.
  kNetworkEthernet = 1,
  kNetworkWifi = 2,
  kNetwork2G = 3,
  kNetwork3G = 4,
  kNetwork4G = 5,
  kNetwork5G = 6,
  kNetworkWWAN = 7,
  kNetworkBluetooth = 8,
  kNetworkNone = 9,
};

enum Result : int32_t {
  kNoError = 0, /** 没有错误 */
  kFatalError = 1,   /** 错误*/
};

namespace internal {
  class AddressTrackerLinux;
}
class INetworkMonitorEventSink;


class NetworkMonitorLinux {
 public:
  NetworkMonitorLinux(INetworkMonitorEventSink* sink);
  virtual ~NetworkMonitorLinux();

 public:
  Result Start();
  Result Stop();
  NetworkType GetCurrentNetworkType();

private:
  bool has_started = false;
  std::unique_ptr<internal::AddressTrackerLinux> address_tracker_linux_;
};


class INetworkMonitorEventSink {
 public:
  virtual ~INetworkMonitorEventSink() = default;

  virtual void OnDeviceNetworkChanged(NetworkType type) = 0;
};

namespace internal {
class AddressTrackerLinux
{
public:
  AddressTrackerLinux();
  AddressTrackerLinux(INetworkMonitorEventSink* sink);
  virtual ~AddressTrackerLinux();

  std::set<int> GetOnlineLinks();
  NetworkType GetCurrentConnectionType();
  void StartTracking();
  void StopTracking();

private:
  bool Init();
  void DeInit();
  void Close();
  void UpdateCurrentConnectionType();
  void ReadMessages();
  void HandleMessage(char* buffer,
                    size_t length,
                    bool* address_changed,
                    bool* link_changed,
                    bool* tunnel_changed);
  void WakeUpThread();

  INetworkMonitorEventSink* sink_;
  std::thread thread_;
  std::atomic_bool thread_stopped_;
  int netlink_fd_ = -1;
  std::mutex online_links_lock_;
  std::set<int> online_links_;
  std::mutex current_connection_type_lock_;
  NetworkType current_connection_type_ = kNetworkUnknown;

  static const int MAX_EVENT_NUMBER = 1024;
  struct epoll_event events_[MAX_EVENT_NUMBER];
  int epoll_fd_ = -1;
  int event_fd_ = -1;
};

class SocketGuard 
{
public:
  SocketGuard(int sockfd);
  virtual ~SocketGuard();

private:
  int sockfd_ = -1;
};

} // namespace internal

  • network_monitor.cpp
#include "network_monitor.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>
#include <linux/if.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <linux/wireless.h>
#include <sys/ioctl.h>
#include <algorithm>
#include <unistd.h>
#include <string.h>
#include <map>
#include <sys/eventfd.h>

NetworkMonitorLinux::NetworkMonitorLinux(INetworkMonitorEventSink* sink) 
{
  address_tracker_linux_ = std::make_unique<internal::AddressTrackerLinux>(sink);
}

NetworkMonitorLinux::~NetworkMonitorLinux() {
  Stop();
}

Result NetworkMonitorLinux::Start() {
  if (has_started) {
    return kFatalError;
  }

  has_started = true;
  if (address_tracker_linux_) {
    address_tracker_linux_->StartTracking();
  }

  return kNoError;
}

Result NetworkMonitorLinux::Stop() {
  if (!has_started) {
    return kFatalError;
  }

  has_started = false;
  if (address_tracker_linux_) {
    address_tracker_linux_->StopTracking();
  }
  
  return kNoError;
}

NetworkType NetworkMonitorLinux::GetCurrentNetworkType() {
  if (address_tracker_linux_) {
    return address_tracker_linux_->GetCurrentConnectionType();
  } else {
    return kNetworkUnknown;
  }
}

namespace internal {

static
char* GetInterfaceName(int interface_index, char* buf) {
  memset(buf, 0, IFNAMSIZ);
  int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  SocketGuard socket_guard(sockfd);
  if (sockfd < 0) {
    return buf;
  }

  struct ifreq ifr = {};
  ifr.ifr_ifindex = interface_index;

  if (ioctl(sockfd, SIOCGIFNAME, &ifr) == 0)
    strncpy(buf, ifr.ifr_name, IFNAMSIZ - 1);
  return buf;
}

static
bool IsTunnelInterface(const char *name) {
  // Linux kernel drivers/net/tun.c uses "tun" name prefix.
  return strncmp(name, "tun", 3) == 0;
}

static
bool IsVirtualNetworkInterface(const char *name) {
  // Remove VMware network interfaces as they're internal and should not be
  // used to determine the network connection type.

  std::string interfce_str(name);
  std::transform(interfce_str.begin(), interfce_str.end(), interfce_str.begin(), ::tolower);
  return interfce_str.find("vmnet") != std::string::npos;
}

static
bool IsDockerNetworkInterface(const char *name) {
  // Remove docker network interfaces as they're internal and should not be
  // used to determine the network connection type.

  std::string interfce_str(name);
  std::transform(interfce_str.begin(), interfce_str.end(), interfce_str.begin(), ::tolower);
  return interfce_str.find("docker") != std::string::npos;
}

static
NetworkType GetInterfaceConnectionType(
    const std::string& ifname) {
  int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  SocketGuard socket_guard(sockfd);
  if (sockfd < 0) {
    return kNetworkUnknown;
  }

  // Test wireless extensions for CONNECTION_WIFI
  struct iwreq pwrq = {};
  strncpy(pwrq.ifr_name, ifname.c_str(), IFNAMSIZ - 1);
  if (ioctl(sockfd, SIOCGIWNAME, &pwrq) != -1)
    return kNetworkWifi;

#if !defined(OS_ANDROID)
  // Test ethtool for CONNECTION_ETHERNET
  struct ethtool_cmd ecmd = {};
  ecmd.cmd = ETHTOOL_GSET;
  struct ifreq ifr = {};
  ifr.ifr_data = &ecmd;
  strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ - 1);
  if (ioctl(sockfd, SIOCETHTOOL, &ifr) != -1)
    return kNetworkEthernet;
#endif  // !defined(OS_ANDROID)

  return kNetworkUnknown;
}

static
NetworkType ConnectionTypeFromInterfaceList(const std::map<int, NetworkType> &online_links_type) {
  bool first = true;
  NetworkType result = kNetworkNone;
  for (auto s : online_links_type) {
    if (first) {
      first = false;
      result = s.second;
    } else if (result != s.second) {
      return kNetworkUnknown;
    }
  }
  return result;
}

AddressTrackerLinux::AddressTrackerLinux()
: sink_(nullptr)
, thread_stopped_(true) {

}

AddressTrackerLinux::AddressTrackerLinux(INetworkMonitorEventSink* sink)
: sink_(sink)
, thread_stopped_(true) {

}

AddressTrackerLinux::~AddressTrackerLinux() {
  StopTracking();
}

void AddressTrackerLinux::DeInit() {
  {
    std::lock_guard<std::mutex> auto_lock(current_connection_type_lock_);
    current_connection_type_ = kNetworkUnknown;
  }

  {
    std::lock_guard<std::mutex> auto_lock(online_links_lock_);
    online_links_.clear();
  }
}

bool AddressTrackerLinux::Init() {
    // domain - AF_NETLINK: Kernel user interface device
    // type - SOCK_RAW: Provides raw network protocol access.
    // protocol - NETLINK_ROUTE: 
    netlink_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (netlink_fd_ < 0) {
      printf("Could not create NETLINK socket\n");
      return false;
    }
    
    // Request notifications.
    struct sockaddr_nl addr = {};
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    // TODO(szym): Track RTMGRP_LINK as well for ifi_type,
    // http://crbug.com/113993
    addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_NOTIFY | RTMGRP_LINK;

    int rv = bind(
        netlink_fd_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
    if (rv < 0) {
      printf("Could not bind NETLINK socket\n");
      Close();
      return false;
    }

    struct sockaddr_nl peer = {};
    peer.nl_family = AF_NETLINK;

    struct {
      struct nlmsghdr header;
      struct rtgenmsg msg;
    } request = {};

    request.header.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
    request.header.nlmsg_type = RTM_GETLINK;
    request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; // must set NLM_F_DUMP, or will receive message error -22
    request.header.nlmsg_pid = getpid();
    request.msg.rtgen_family = AF_UNSPEC;

    rv = sendto(netlink_fd_, &request, request.header.nlmsg_len,
                            0, reinterpret_cast<struct sockaddr*>(&peer),
                            sizeof(peer));
    if (rv < 0) {
      printf("Could not send NETLINK request\n");
      Close();
      return false;
    }

    ReadMessages();

    epoll_fd_ = epoll_create(5);
    if (epoll_fd_ < 0) {
      printf("Could not create epoll\n");
      return false;
    }

    struct epoll_event event;
    event.events = EPOLLIN | EPOLLET; // must read all data once in et mode 
    event.data.fd = netlink_fd_;
    epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, netlink_fd_, &event);

    event_fd_ = eventfd(0, 0);
    if (event_fd_ < 0) {
      printf("Could not create eventfd\n");
      return false;
    }
    event.data.fd = event_fd_;
    epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, event_fd_, &event);

    return true;
}

void AddressTrackerLinux::Close() {
  if (event_fd_ >= 0 && close(event_fd_) < 0) {
    printf("Could not close event fd\n");
  }
  event_fd_ = -1;
  
  if (epoll_fd_ >= 0 && close(epoll_fd_) < 0) {
    printf("Could not close epoll fd\n");
  }
  epoll_fd_ = -1;
  
  if (netlink_fd_ >= 0 && close(netlink_fd_) < 0) {
    printf("Could not close NETLINK socket\n");
  }
  netlink_fd_ = -1;
}

void AddressTrackerLinux::UpdateCurrentConnectionType() {
  std::set<int> online_links = GetOnlineLinks();
  std::map<int, NetworkType> online_links_type;

  // Strip out tunnel | virtual | docker interfaces from online_links
  for (auto it = online_links.begin(); it != online_links.end(); ) {
    char buf[IFNAMSIZ] = {0};
    GetInterfaceName(*it, buf);
    if (IsTunnelInterface(buf) || 
        IsVirtualNetworkInterface(buf) ||
        IsDockerNetworkInterface(buf)) {
      it = online_links.erase(it);
    } else {
      online_links_type[*it] = GetInterfaceConnectionType(std::string(buf));
      ++it;
    }
  }

  NetworkType type = ConnectionTypeFromInterfaceList(online_links_type);
  std::lock_guard<std::mutex> auto_lock(current_connection_type_lock_);
  current_connection_type_ = type;
  if (sink_) {
    sink_->OnDeviceNetworkChanged(current_connection_type_);
  }
}


NetworkType
AddressTrackerLinux::GetCurrentConnectionType() {
  std::lock_guard<std::mutex> auto_lock(current_connection_type_lock_);
  return current_connection_type_;
}

std::set<int> AddressTrackerLinux::GetOnlineLinks() {
  std::lock_guard<std::mutex> auto_lock(online_links_lock_);
  return online_links_;
}

void AddressTrackerLinux::ReadMessages() {
  bool address_changed = false;
  bool link_changed = false;
  bool tunnel_changed = false;
  char buffer[4096];
  bool first_loop = true;
  for (;;) {
    int rv = recv(netlink_fd_,
                buffer,
                sizeof(buffer),
                // Block the first time through loop.
                first_loop ? 0 : MSG_DONTWAIT);
    first_loop = false;
    if (rv == 0) {
      printf("Unexpected shutdown of NETLINK socket\n");
      return;
    }
    if (rv < 0) {
      if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
        break;
      printf("Failed to recv from netlink socket\n");
      return;
    }
    HandleMessage(buffer, rv, &address_changed, &link_changed, &tunnel_changed);
  }
  if (link_changed || address_changed) {
      UpdateCurrentConnectionType();
  }
}

void AddressTrackerLinux::HandleMessage(char* buffer,
                                        size_t length,
                                        bool* address_changed,
                                        bool* link_changed,
                                        bool* tunnel_changed) {
  if (!buffer || length <= 0) {
    return;
  }
  for (struct nlmsghdr* header = reinterpret_cast<struct nlmsghdr*>(buffer);
       NLMSG_OK(header, length);
       header = NLMSG_NEXT(header, length)) {
    switch (header->nlmsg_type) {
      case NLMSG_DONE:
        return;
      case NLMSG_ERROR: {
        const struct nlmsgerr* msg =
            reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(header));
        printf("Unexpected netlink error: %d\n", msg->error);
      } return;
      case RTM_NEWADDR: {
        // todo
      } break;
      case RTM_DELADDR: {
        // todo
      } break;
      case RTM_NEWLINK: {
        const struct ifinfomsg* msg =
            reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
        if (!(msg->ifi_flags & IFF_LOOPBACK) && (msg->ifi_flags & IFF_UP) &&
            (msg->ifi_flags & IFF_LOWER_UP) && (msg->ifi_flags & IFF_RUNNING)) {
          std::lock_guard<std::mutex> auto_lock(online_links_lock_);
          if (online_links_.insert(msg->ifi_index).second) {
            *link_changed = true;
          }
        } else {
          std::lock_guard<std::mutex> auto_lock(online_links_lock_);
          if (online_links_.erase(msg->ifi_index)) {
            *link_changed = true;
          }
        }
      } break;
      case RTM_DELLINK: {
        const struct ifinfomsg* msg =
            reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
        std::lock_guard<std::mutex> auto_lock(online_links_lock_);
        if (online_links_.erase(msg->ifi_index)) {
          *link_changed = true;
        }
      } break;
      default:
        break;
    }
  }
}

void AddressTrackerLinux::StartTracking() {
  if (!thread_stopped_) {
    return;
  }
  thread_stopped_ = false;
  if (!Init()) {
    printf("Init failed\n");
    return;
  }

  thread_ = std::thread([this]() {
      while (!thread_stopped_) {
          int ret = epoll_wait(epoll_fd_, events_, MAX_EVENT_NUMBER, -1);
          if (ret < 0) {
            printf("epoll wait failure\n");
            break;
          }
          auto IsEventFdReady = [] (int event_fd, int event_numbers, struct epoll_event *events) {
              for (int i = 0; i < event_numbers; i++) {
                if (event_fd == events[i].data.fd) {
                  return true;
                }
              }
              return false;
          };
          if (IsEventFdReady(event_fd_, ret, events_)) {
            // used to wake up thread
          } else {
            ReadMessages();
          }
      }
  });
  pthread_setname_np(thread_.native_handle(), "network_monitor");
}

void AddressTrackerLinux::StopTracking() {
  if (thread_stopped_) {
    return;
  }

  thread_stopped_ = true;
  WakeUpThread();
  if (thread_.joinable()) {
    thread_.join();
  }
  Close();
  DeInit();
}

void AddressTrackerLinux::WakeUpThread() {
  eventfd_write(event_fd_, 1);
}

SocketGuard::SocketGuard(int sockfd) 
: sockfd_(sockfd) {

}

SocketGuard::~SocketGuard() {
  if (sockfd_ >= 0) {
      close(sockfd_);
  } else {
    printf("Failed to create socket\n");
  }
  sockfd_ = -1;
}

} // namespace internal
  • 测试代码
#include "network_monitor.h"
#include <iostream>
#include <unistd.h>

class NetworkMonitorEventSink : public INetworkMonitorEventSink {
 public:
  virtual ~NetworkMonitorEventSink() = default;

  void OnDeviceNetworkChanged(NetworkType type) override {
    std::cout << "[sink] Device Network Changed to type " << type << std::endl;;
  }
};


int main(void)
{
    NetworkMonitorEventSink sink;
    NetworkMonitorLinux networkMonitor(&sink);

    networkMonitor.Start();
    std::cout << "Device Network type is " << networkMonitor.GetCurrentNetworkType() << std::endl;

    std::cout << "输入回车结束" << std::endl;
    getchar();

    networkMonitor.Stop();

    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于netlink的Linux Network Monitor实现 的相关文章

  • 将 jar 作为 Linux 服务运行 - init.d 脚本在启动应用程序时卡住

    我目前正在致力于在 Linux VM 上实现一个可运行的 jar 作为后台服务 我已经使用了找到的例子here https gist github com shirish4you 5089019作为工作的基础 并将 start 方法修改为
  • ftrace:仅打印trace_printk()的输出

    是否可以只转储trace printk 输出于trace文件 我的意思是过滤掉函数跟踪器 或任何其他跟踪器 中的所有函数 一般来说 您可以在选项目录中关闭选项 sys kernel debug tracing options Use ls显
  • 如何确保应用程序在 Linux 上持续运行

    我试图确保脚本在开发服务器上保持运行 它会整理统计数据并提供网络服务 因此它应该会持续存在 但一天中有几次 它会因未知原因而消失 当我们注意到时 我们只需再次启动它 但这很麻烦 并且某些用户没有权限 或专有技术 来启动它 作为一名程序员 我
  • 删除 Git 存储库,但保留所有文件

    在我使用 Linux 的过程中的某个时刻 我决定将我的主目录中的所有内容都放入源代码管理中是个好主意 我不是在问这是否是一个好主意 我是在问如何撤销它 删除存储库的原因是我最近安装了 Oh My Zsh 而且我非常喜欢它 问题是我的主目录有
  • 当 grep "\\" XXFile 我得到“尾随反斜杠”

    现在我想查找是否有包含 字符的行 我试过grep XXFile但它暗示 尾随反斜杠 但当我尝试时grep XXFile没关系 谁能解释一下为什么第一个案例无法运行 谢谢 区别在于 shell 处理反斜杠的方式 当你写的时候 在双引号中 sh
  • MySQL 中的创建/写入权限

    我的设备遇到一些权限问题SELECT INTO OUTFILE陈述 当我登录数据库并执行简单的导出命令时 例如 mysql gt select from XYZ into outfile home mropa Photos Desktop
  • 如何减去两个 gettimeofday 实例?

    我想减去两个 gettimeofday 实例 并以毫秒为单位给出答案 这个想法是 static struct timeval tv gettimeofday tv NULL static struct timeval tv2 gettime
  • 在两次之间每分钟执行一次 Cronjob

    我需要在 crontab 中每分钟运行一个 bash 脚本8 45am and 9 50am每天的 Code 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 8 home pull sh gt ho
  • 进程退出后 POSIX 名称信号量不会释放

    我正在尝试使用 POSIX 命名信号量进行跨进程同步 我注意到进程死亡或退出后 信号量仍然被系统打开 在进程 打开它 死亡或退出后是否有办法使其关闭 释放 早期的讨论在这里 当将信号量递减至零的进程崩溃时 如何恢复信号量 https sta
  • 确定我可以向文件句柄写入多少内容;将数据从一个 FH 复制到另一个 FH

    如何确定是否可以将给定数量的字节写入文件句柄 实际上是套接字 或者 如何 取消读取 我从其他文件句柄读取的数据 我想要类似的东西 n how much can I write w handle n read r handle buf n a
  • 如何查询X11显示分辨率?

    这似乎是一个简单的问题 但我找不到答案 如何查询 通过 X11 存在哪些监视器及其分辨率 查看显示宏 http tronche com gui x xlib display display macros html and 屏幕宏 http
  • PyQt5 - 无法使用 QVideoWidget 播放视频

    from PyQt5 QtWidgets import from PyQt5 QtMultimedia import from PyQt5 QtMultimediaWidgets import from PyQt5 QtCore impor
  • 如何允许应用程序声明“https”方案 URI? (即如何从 https URL 打开桌面应用程序?)

    目前我正在尝试为 OAuth 2 0 授权流程创建一个客户端 实际上是一个本机应用程序 并且在规范中就在这儿 https www rfc editor org rfc rfc8252 section 7 2据说有 3 种方法来处理重定向 U
  • git 错误:无法处理 https

    当我尝试使用 git clone 时https xxx https xxx我收到以下错误我不处理协议 https 有人可以帮我吗 完整消息 dementrock dementrock A8Se git 克隆https git innosta
  • Python 脚本作为 Linux 服务/守护进程

    Hallo 我试图让 python 脚本作为服务 守护进程 在 ubuntu linux 上运行 网络上存在多种解决方案 例如 http pypi python org pypi python daemon http pypi python
  • 使用 plistBuddy 获取值数组

    var keychain access groups declare a val usr libexec PlistBuddy c Print var sample plist echo val echo val 0 Ouput Array
  • Awk - 计算两个文件之间的每个唯一值和匹配值

    我有两个文件 首先 我尝试获取第 4 列中每个唯一字段的计数 然后匹配第二个文件的第二列中的唯一字段值 File1 第 4 列的每个唯一值和 File2 第 2 列包含我需要在两个文件之间匹配的值 所以本质上 我试图 gt 如果 file2
  • 如何在 GNU/Linux 上设置 Subversion (SVN) 服务器 - Ubuntu [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我有一台运行 Ubuntu 的笔记本电脑 我想将其用作 Subversion 服务器 既让我自己在本地承诺 也让其他人远程承诺 要使其
  • 在 Ubuntu 中找不到 X11/Xlib.h

    我试图在 Linux 上使用 open gl 编写一个相当简单的程序 但在编译时它说 编译拇指 egl 我对 GL 完全陌生 不知道出了什么问题 快速搜索使用 apt search Xlib h 打开 libx11 dev 包 但纯 Ope
  • Raspberry 交叉编译 - 执行程序以“分段错误”结束

    我有一个自己编写的程序 我想从我的 x86 机器上为 Raspberry Pi 构建它 我正在使用 eclipse 生成的 makefile 并且无法更改此内容 我已经阅读了 CC for raspi 的教程 Hackaday 链接 htt

随机推荐