Linux驱动开发(十八)---网络(网卡)驱动学习

2023-05-16

前文回顾

《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
《Linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《Linux驱动开发(七)—树莓派按键驱动开发》
《Linux驱动开发(八)—树莓派SR04驱动开发》
《Linux驱动开发(九)—树莓派I2C设备驱动开发(BME280)》
《Linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
《Linux驱动开发(十一)—树莓派SPI驱动学习(OLED)》
《Linux驱动开发(十二)—树莓派framebuffer学习(改造OLED)》
《Linux驱动开发(十三)—USB驱动HID开发学习(鼠标)》
《Linux驱动开发(十四)—USB驱动开发学习(键盘+鼠标)》
《Linux驱动开发(十五)—如何使用内核现有驱动(显示屏)》
《Linux驱动开发(十六)—块设备驱动》
《Linux驱动开发(十七)—树莓派PWM驱动》

今天来学一下驱动中的第三类设备,网络设备的驱动。边学边写,慢慢理解。
在这里插入图片描述

网络设备驱动程序结构

Linux网络设备驱动程序的体系结构如图所示,从上到下可以划分为4层,依次为网络协议接口层、网络设备接口层、提供实际功能的设备驱动功能层以及网络设备与媒介层,这4层的作用如下所示:

  1. 网络协议接口层
    向网络层协议提供统一的数据包收发接口,不论上层协议是ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()数接收数据。这一层的存在使得上层协议独立于具体的设备。
  2. 网络设备接口层
    向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
  3. 设备驱动功能层
    设备驱动功能层的各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。
  4. 网络设备与媒介层
    网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。
    在这里插入图片描述
    以上内容来自《Linux设备驱动开发详解》,其中下面这句就是关键
    在设计具体的网络设备驱动程序时,我们需要完成的主要工作是
    编写设备驱动功能层的相关函数

    填充net device数据结构
    的内容
    并将net device注册入内核。
    看看,三个核心操作。
    在这里插入图片描述

虚拟网卡

来看一个虚拟网卡的例子,就是直接构造软件网卡,来看一下实现过程

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/netdev_features.h>
 
static struct net_device *virt_net;
   
static void virt_rs_packet(struct sk_buff *skb, struct net_device *dev)
{
	unsigned char *type;
	struct iphdr *ih;
	__be32 *saddr, *daddr, tmp;
	unsigned char tmp_dev_addr[ETH_ALEN];
	struct ethhdr *ethhdr;
	struct sk_buff *rx_skb;
	int ret;
 
	//对调ethhdr结构体 "源/目的"MAC地址*/
	ethhdr = (struct ethhdr *)skb->data;
	memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
	memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
	memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);

	//对调iphdr结构体"源/目的" IP地址
	ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
	saddr = &ih->saddr;
	daddr = &ih->daddr;
	tmp = *saddr;
	*saddr = *daddr;
	*daddr = tmp;
        
    ih->check=0;
    ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
	
	//之前是发送ping包0x08,需要改为0x00,表示接收ping包
	type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
	*type = 0; 
 
	rx_skb = dev_alloc_skb(skb->len + 2);
	skb_reserve(rx_skb, 2);
	
	memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
	rx_skb->dev = dev;	
	rx_skb->ip_summed = CHECKSUM_UNNECESSARY;
	rx_skb->protocol = eth_type_trans(rx_skb, dev);
	ret=netif_rx(rx_skb);
	
	dev->stats.rx_packets++;        
	dev->stats.rx_bytes += skb->len;
	pr_info("rx_packets=%ld rx_bytes=%ld ret=%d\n",dev->stats.rx_packets,dev->stats.rx_bytes,ret);
}
 
static int virt_send_packet(struct sk_buff *skb, struct net_device *dev)
{
	netif_stop_queue(dev);
	virt_rs_packet(skb,dev);
	dev_kfree_skb(skb);
	dev->stats.tx_packets++; 
	dev->stats.tx_bytes+=skb->len; 
	pr_info("tx_packets=%ld tx_bytes=%ld\n",dev->stats.tx_packets,dev->stats.tx_bytes);
	netif_wake_queue(dev); 
	return NETDEV_TX_OK;
}
 
static int set_mac_address(struct net_device *dev,void *p)
{
	struct sockaddr *addr = p;
	pr_info("set_mac_address\n");
	if (netif_running(dev))
	{
		return -EBUSY;
	}
	memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
	return 0;
}
void virt_tx_timeout(struct net_device *net,unsigned int txqueue)
{
	pr_info("virt_tx_timeout\n");
}
 
static const struct net_device_ops net_ops =
{
	.ndo_start_xmit		= virt_send_packet,
	.ndo_set_mac_address    =set_mac_address,
	.ndo_tx_timeout		= virt_tx_timeout,
};
 
static int virt_net_init(void){
	virt_net= alloc_netdev(sizeof(struct net_device), "virt_net", NET_NAME_UNKNOWN,ether_setup);
	virt_net->netdev_ops= &net_ops;
	virt_net->flags = IFF_NOARP;
 
	virt_net->dev_addr[0] = 0x88;
	virt_net->dev_addr[1] = 0x88;
	virt_net->dev_addr[2] = 0x88;
	virt_net->dev_addr[3] = 0x88;
	virt_net->dev_addr[4] = 0x88;
	virt_net->dev_addr[5] = 0x88;
 
	register_netdev(virt_net);
	return 0;
}
 
static void virt_net_exit(void)
{
    unregister_netdev(virt_net);
    free_netdev(virt_net);   
}
 
module_init(virt_net_init);
module_exit(virt_net_exit);
 
MODULE_LICENSE("GPL");


编译加载之后,就有了这么一个网卡

在这里插入图片描述
不过这个169.254.106.62,为什么他会有这么一个IP

为什么某些电脑获得了形如169.254..之类的IP地址呢?
这说明这些电脑根本未能获得DHCP服务。如DHCP服务器太忙导致无法及时给客户机应答,使客户机认为网络里不存在DHCP服务器。这时,微软的操作系统会自动查找一个形如169.254.*.*的IP地址分配给客户机

不过不知道为啥我这个linux也吃了这么一个瓜
在这里插入图片描述
不过还是可以ping通的
在这里插入图片描述
为啥能ping通,因为我们在代码里给它返回了正确应答。

虚拟网卡代码分析

其中,在模块初始化函数中就做了前面说的三个步骤,过程也很简单。

  1. 申请一个net_device结构的内存
virt_net= alloc_netdev(sizeof(struct net_device), "virt_net", NET_NAME_UNKNOWN,ether_setup);
  1. 填充数据

包括操作netdev_ops和一些参数,例如下面的网卡mac

virt_net->netdev_ops= &net_ops;
virt_net->flags = IFF_NOARP;

virt_net->dev_addr[0] = 0x88;
virt_net->dev_addr[1] = 0x88;
virt_net->dev_addr[2] = 0x88;
virt_net->dev_addr[3] = 0x88;
virt_net->dev_addr[4] = 0x88;
virt_net->dev_addr[5] = 0x88;
  1. 注册网络设备到内核
register_netdev(virt_net);

net_device的数据结构很庞大,感兴趣的可以在netdevice.h中查看一下,本身就是网络设备的参数和状态信息。其中的操作结构net_device_ops也是一样的庞大,这个里面是操作处理函数,例如接收到数据的回调函数。

那在这个虚拟设备中,就只是封装了三个接口

static const struct net_device_ops net_ops =
{
	.ndo_start_xmit		= virt_send_packet,
	.ndo_set_mac_address    =set_mac_address,
	.ndo_tx_timeout		= virt_tx_timeout,
};
netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb, struct net_device *dev);

功能:当需要传输数据包时调用。返回NETDEV_TX_OK。可以返回NETDEV_TX_BUSY,但应在这之前停止队列;它适用于过时的冷门的设备,但如果您返回NETDEV_TX_BUSY,堆栈确实会做大量无用的工作。必填的;不能为空。
这翻译是不是有点那味
在这里插入图片描述

int (*ndo_set_mac_address)(struct net_device *dev, void *addr);

功能:当MAC地址需要改变时,如果未定义此接口,则MAC地址无法更改。

void (*ndo_tx_timeout)(struct net_device *dev, unsigned int txqueue);

功能:当发送器未对dev->watchdog ticks取得任何进展时使用的回调。

那么整体来看,这个例子的功能,就是在数据来临之后,一旦调用virt_send_packet函数,就把收到的数据,利用本地函数virt_rs_packet将数据的源目的地址调换,修改成应答,发送出去,在ping的时候,就会有应答的反应了。
参照icmp的格式。
把请求
在这里插入图片描述
变成了应答
在这里插入图片描述
所以ping的时候,就会有反应。
在这里插入图片描述

学习RTL8187驱动

不过前面的虚拟网卡比较简单,内核中已经有好多网卡的驱动,我这有一个usb的无线网卡,不妨来学习一下,型号是rtl8187,所有代码都在这个路径下
在这里插入图片描述
其中dev.c是模块的主文件,创建模块,probe函数等都在这里。
首先是作为usb设备进行注册。毕竟是usb模块嘛
在这里插入图片描述
在probe函数中,用到了一个ieee80211的子系统,具体学习可以参考《Linux无线驱动简介及mac80211源码分析》

mac80211:是一个Linux内核子系统,是驱动开发者可用于为SoftMAC无线设备写驱动的框架

不过我看了半天,还是没看太懂,过于复杂,不做展示
在这里插入图片描述

在probe函数中,主要相关是下面三个函数的使用。

申请

dev = ieee80211_alloc_hw(sizeof(*priv), &rtl8187_ops);

配置

ieee80211_hw_set(dev, RX_INCLUDES_FCS);
……

注册

err = ieee80211_register_hw(dev);

在申请步骤,和rtl8187相关的操作都在rtl8187_ops结构中
在这里插入图片描述
这里面的大部分操作,都是和usb读写相关的,举个例子tx函数

 * @tx: Handler that 802.11 module calls for each transmitted frame.
 *	skb contains the buffer starting from the IEEE 802.11 header.
 *	The low-level driver should send the frame out based on
 *	configuration in the TX control data. This handler should,
 *	preferably, never fail and stop queues appropriately.
 *	Must be atomic.

最终调用到

static void rtl8187_tx(struct ieee80211_hw *dev,
		       struct ieee80211_tx_control *control,
		       struct sk_buff *skb)

最终也是通过usb结构urb发送出去
在这里插入图片描述

然后就是中间的相关配置整个模块,里面用到了usb传输方式配置寄存器,也用了eep配置网卡中的数据。
最终注册函数
ieee80211_register_hw->
ieee80211_if_add->
cfg80211_register_netdevice->
register_netdevice

完成了网卡的注册。

整个过程目前只捋顺了这些东西,要想看懂每一行,估计也是很困难,只是把关键的操作找出来了。
在这里插入图片描述

可以再来看一个更简单的协议

学习RTL8150驱动

首先也是注册一个usb设备
在这里插入图片描述
然后看probe函数

static int rtl8150_probe(struct usb_interface *intf,
			 const struct usb_device_id *id)
{
	struct usb_device *udev = interface_to_usbdev(intf);
	rtl8150_t *dev;
	struct net_device *netdev;

	netdev = alloc_etherdev(sizeof(rtl8150_t));
	if (!netdev)
		return -ENOMEM;

	dev = netdev_priv(netdev);

	dev->intr_buff = kmalloc(INTBUFSIZE, GFP_KERNEL);
	if (!dev->intr_buff) {
		free_netdev(netdev);
		return -ENOMEM;
	}

	tasklet_setup(&dev->tl, rx_fixup);
	spin_lock_init(&dev->rx_pool_lock);

	dev->udev = udev;
	dev->netdev = netdev;
	netdev->netdev_ops = &rtl8150_netdev_ops;
	netdev->watchdog_timeo = RTL8150_TX_TIMEOUT;
	netdev->ethtool_ops = &ops;
	dev->intr_interval = 100;	/* 100ms */

	if (!alloc_all_urbs(dev)) {
		dev_err(&intf->dev, "out of memory\n");
		goto out;
	}
	if (!rtl8150_reset(dev)) {
		dev_err(&intf->dev, "couldn't reset the device\n");
		goto out1;
	}
	fill_skb_pool(dev);
	set_ethernet_addr(dev);

	usb_set_intfdata(intf, dev);
	SET_NETDEV_DEV(netdev, &intf->dev);
	if (register_netdev(netdev) != 0) {
		dev_err(&intf->dev, "couldn't register the device\n");
		goto out2;
	}

	dev_info(&intf->dev, "%s: rtl8150 is detected\n", netdev->name);

	return 0;

out2:
	usb_set_intfdata(intf, NULL);
	free_skb_pool(dev);
out1:
	free_all_urbs(dev);
out:
	kfree(dev->intr_buff);
	free_netdev(netdev);
	return -EIO;
}

是不是和虚拟网卡的有点类似了,简单清楚
在这里插入图片描述
几个核心操作

  1. 申请设备的私有空间
	netdev = alloc_etherdev(sizeof(rtl8150_t));

这里就是开发网络驱动的核心部分,这个netdev是一个私有空间,不同类型的网卡,通常结构不一样,里面存放的就是针对这个设备驱动的数据
例如

在这里插入图片描述

  1. 注册接收终端下半部
	tasklet_setup(&dev->tl, rx_fixup);
  1. 配置操作函数
	netdev->netdev_ops = &rtl8150_netdev_ops;
  1. 甚至于更相似的设置网卡mac地址
	set_ethernet_addr(dev);
  1. 最后注册
	if (register_netdev(netdev) != 0) {
		dev_err(&intf->dev, "couldn't register the device\n");
		goto out2;
	}

这个感觉还是清楚多了。不过实际上,可能真的没什么机会去写一个完整的驱动,应该是以移植驱动到一个嵌入式平台为主吧。
在这里插入图片描述

结束语

最近的大事就是辅助驾驶出事故,辅助驾驶在目前来看还是不够成熟,辅助驾驶还得驾驶员紧盯着,也不知道在辅助什么,而且都知道无法识别静态物体,反正是做不到自动驾驶的阶段,还是都别用的比较好。

首先,路面上临时出现的路障、三角、警示牌、施工作业区由于出现的周期太短,往往无法被现成的自动驾驶高精地图收录。于是识别上述物体的任务落在本就压力山大的自动驾驶机器头上。
机器感知与人类感知的逻辑不同。人眼可以清晰看到前方路标、路牌、前车尾灯并将其分类,而机器识别的结果只能由数据和算法决定。开放道路场景千变万化,只要物体简单变换外观,就必须重新识别。机器或许能识别出一个推自行车的行人,但难以在短时间内识别一个戴皮卡丘头套推车的行人。数据场景库的丰富度和算法质量不足以应付这一罕见场景。
静止物体的感知和识别是更有难度的工作。在自动驾驶感知系统中,雷达“看到”的是点云,摄像头“看到”的是图像像素,二者数据特征不同,需要复杂的融合过程。反观移动的目标点,由于一直在变化,相对容易判断。而静止障碍物混在静止路牌、路标、绿植中,只有经过多轮筛选才能标识出。一旦算法不够成熟,很容易出现某一传感器识别出障碍物,但被承担巨大高速运转压力的算法当作错误或不重要数据直接过滤。

你们想想,你带着老婆,出了城,吃着火锅还唱着歌!突然就撞了人!
在这里插入图片描述
不过,就目前的人工智能来说,代替财务应该是没啥问题的。但是为啥没有财务被代替呢?因为人工智能,不会坐牢。

在这里插入图片描述

今天是中元节,还是要早点回家,本来阳气就不足,可别被偷走了。在这里插入图片描述

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

Linux驱动开发(十八)---网络(网卡)驱动学习 的相关文章

  • 内核驱动程序从用户空间读取正常,但写回始终为 0

    因此 我正在努力完成内核驱动程序编程 目前我正在尝试在应用程序和内核驱动程序之间构建简单的数据传输 我使用简单的字符设备作为这两者之间的链接 并且我已成功将数据传输到驱动程序 但我无法将有意义的数据返回到用户空间 内核驱动程序如下所示 in
  • linux新手关于嵌入式linux设备驱动的问题

    最近在研究linux驱动 正如我读过的那些文章所说 设备驱动程序模块很可能会根据内核的需要自动加载 因此我想知道内核如何确定为特定设备 声卡 I2C spi 设备 等 我也无法彻底想象内核如何在启动时检测每个硬件设备 与嵌入式linux相关
  • 段错误...关于你好世界

    这段代码非常简单 但我在 x86 64 Linux 系统上遇到了段错误 这让我很烦恼 刚开始接触asm 请耐心等待 与 NASM 组装nasm f elf64 test asm 与连接ld o test test o SECTION tex
  • 在 scapy 中通过物理环回发送数据包

    我最近发现了 Scapy 它看起来很棒 我正在尝试查看 NIC 上物理环回模块 存根上的简单流量 但是 Scapy sniff 没有给出任何结果 我正在做的发送数据包是 payload data 10 snf sniff filter ic
  • 使用 ioctl 在 C++ 中以编程方式添加路由

    我编写了简单的 C 函数 添加了新路线 void addRoute int fd socket PF INET SOCK DGRAM IPPROTO IP struct rtentry route memset route 0 sizeof
  • 更新Linux中的包含路径

    我的 my path to file 文件夹中有几个头文件 我知道如何将这些文件包含在新的 C 程序中 但每次我都需要在包含它之前输入头文件的完整路径 我可以在linux中设置一些路径变量 以便它自动查找头文件吗 您可以创建一个 makef
  • 为什么 OS X 和 Linux 之间的 UTF-8 文本排序顺序不同?

    我有一个包含 UTF 8 编码文本行的文本文件 mac os x cat unsorted txt foo foo 津 如果它有助于重现问题 这里是文件中确切字节的校验和和转储 以及如何自己生成文件 在 Linux 上 使用base64 d
  • Docker忽略limits.conf(试图解决“打开文件太多”错误)

    我正在运行一个 Web 服务器 该服务器正在处理数千个并发 Web 套接字连接 为了实现这一点 在 Debian linux 我的基本镜像是 google debian wheezy 在 GCE 上运行 上 打开文件的默认数量设置为 100
  • 如何并行执行4个shell脚本,我不能使用GNU并行?

    我有4个shell脚本dog sh bird sh cow sh和fox sh 每个文件使用 xargs 并行执行 4 个 wget 来派生一个单独的进程 现在我希望这些脚本本身能够并行执行 由于某些我不知道的可移植性原因 我无法使用 GN
  • LINUX:如何锁定内存中进程的页面

    我有一个 LINUX 服务器 运行一个具有大量内存占用的进程 某种数据库引擎 该进程分配的内存太大 需要将其中一部分换出 换出 我想做的是将所有其他进程 或正在运行的进程的子集 的内存页面锁定在内存中 以便只有数据库进程的页面被换出 例如
  • 如何从 C++ 程序中重新启动 Linux?

    我有一个 Qt 4 GUI 我需要在下拉菜单中提供一个选项 允许用户选择重新启动计算机 我意识到这对于以其他方式重新启动计算机的能力来说似乎是多余的 但选择需要保留在那里 我尝试使用 system 来调用以下内容 suid root she
  • 在汇编中使用 printf 会导致管道传输时输出为空,但可以在终端上使用

    无输出 https stackoverflow com questions 54507957 printf call from assembly do not print to stdout即使在终端上 当输出不包含换行符时也有相同的原因
  • 如何才能将 TCP 连接返回到同一端口?

    机器是 RHEL 5 3 内核 2 6 18 有时我在 netstat 中注意到我的应用程序有连接 建立了 TCP 连接本地地址 and 国外地址是一样的 其他人也报告了同样的问题 症状与链接中描述的相同 客户端连接到本地运行的服务器的端口
  • awk 在循环中使用时不打印任何内容[重复]

    这个问题在这里已经有答案了 我有一堆使用 file 1 a 1 txt 格式的文件 如下所示 A 1 B 2 C 3 D 4 并使用以下命令添加包含每个文件名称的新列 awk print FILENAME NF t 0 file 1 a 1
  • ssh 连接超时

    我无法在 git 中 ssh 到 github bitbucket 或 gitlab 我通常会收到以下错误消息 如何避免它 输出 ssh T email protected cdn cgi l email protection i ssh
  • ioctl 命令的用户权限检查

    我正在实现 char 驱动程序 Linux 并且我的驱动程序中有某些 IOCTL 命令仅需要由 ADMIN 执行 我的问题是如何在 ioctl 命令实现下检查用户权限并限制非特权用户访问 IOCTL 您可以使用bool capable in
  • 在内核代码中查找函数的最佳方法[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我开始浏览内核代码 遇到的一件事是如何跟踪函数调用 结构定义等 有没有一种好的方法可以快速跳转到函数定义并退出 我尝试过 Source N
  • 为什么 Linux 原始套接字的 RX 环大小限制为 4GB?

    背景 我试图mmap 我的原始套接字的 RX 环形缓冲区64 bitLinux 应用程序 我的环由 4096 个块组成 每个块大小为 1MB 总共 4GB 请注意 每个 1MB 块中可以有许多帧 如果您好奇 请参阅此文档了解背景信息 htt
  • Linux 中 m 标志和 o 标志将存储在哪里

    我想知道最近收到的路由器通告的 m 标志和 o 标志的值 从内核源代码中我知道存储了 m 标志和 o 标志 Remember the managed otherconf flags from most recently received R
  • 有没有一种快速方法可以从 Jar/war 中删除文件,而无需提取 jar 并重新创建它?

    所以我需要从 jar war 文件中删除一个文件 我希望有类似 jar d myjar jar file I donot need txt 的内容 但现在我能看到从 Linux 命令行执行此操作的唯一方法 不使用 WinRAR Winzip

随机推荐

  • Android Studio里面Failed to resolve: 包名 解决方式

    Android Studio里面Failed to resolve 包名 解决方式 就像以下这种问题 Failed to resolve io reactivex rxjava 1 1 2 Failed to resolve io reac
  • 自定义View实战(一) 汽车速度仪表盘

    自定义View实战 xff08 一 xff09 汽车速度仪表盘 转载请以链接形式标明出处 xff1a http blog csdn net lxk 1993 article details 51373269 本文出自 lxk 1993的博客
  • java.lang.UnsatisfiedLinkError 解决方法

    就像这样的错误 java lang UnsatisfiedLinkError dalvik system PathClassLoader DexPathList zip file 34 data app com pckgname live
  • 关于魅族手机 安装APP提示安装失败 更新包不兼容的解决方法

    转载请以链接形式标明出处 xff1a http blog csdn net lxk 1993 article details 52943855 本文出自 lxk 1993的博客 xff1b 关于魅族手机安装app提示安装失败 xff0c 更
  • 2016过去了

    15年本来准备些总结的 xff0c 最后又不了了之了 xff0c 这次2016也拖到今天才来写 拖延症患者 2016年 前半年在一家外包公司工作 xff0c 期间做了几个外包项目 xff0c 但是并没有什么用户在用 xff0c 做了等于白做
  • 视频驱动V4L2子系统驱动架构-框架

    V4L2驱动框架 v4l2驱动架构如图所示 xff0c v4l2也就是video for linux two xff0c 那么也就是说还有One了 xff0c v4l2前面还有v4l 图中芯片模块对应Soc的各个子模块 xff0c vide
  • ROS_gazebo 设置/获取模型状态 set_model_state/get_model_state

    版权声明 xff1a 本文为CSDN博主 penge666 的原创文章 xff0c 遵循CC 4 0 by sa版权协议 xff0c 转载请附上原文出处链接及本声明 原文链接 xff1a https blog csdn net penge6
  • ROS中package.xml文件标签

    原地址 xff1a https www cnblogs com qixianyu p 6669687 html 目录 概述格式2 xff08 推荐 xff09 基本结构所需标签依赖关系Metapackages附加标签格式1 xff08 遗产
  • Linux驱动开发(十七)---树莓派PWM驱动

    前文回顾 Linux驱动开发 xff08 一 xff09 环境搭建与hello world Linux驱动开发 xff08 二 xff09 驱动与设备的分离设计 Linux驱动开发 xff08 三 xff09 设备树 Linux驱动开发 x
  • Gazebo仿真中.sdf/.world文件标签

    sdf 文件 详细代码 xff1a lt xml version 61 34 1 0 34 gt lt sdf version 61 34 1 5 34 gt lt model name 61 34 cafe 34 gt lt static
  • Jetson TX2核心板系统烧录、烧写

    1 从官网https developer nvidia com embedded jetpack 中下载sdkmanager xff0c 需登录事先在官网注册的账号 xff08 免费 xff09 xff0c 但Jetpack4 3版本一直无
  • C语言实现汉诺塔详细步骤(递归与非递归)及代码

    前言 C语言汉诺塔问题是一个经典的问题 xff0c 在学习编程的初学者中非常流行 它涉及到了递归的思想 xff0c 能够帮助我们理解递归的基本原理 首先 xff0c 我们来了解一下汉诺塔的问题 汉诺塔问题是指 xff1a 有三根柱子A B
  • C语言 | 输出月份的英文

    要成为绝世高手 xff0c 并非一朝一夕 xff0c 除非是天生武学奇才 xff0c 但是这种人 万中无一 包租婆 这道理放在C语言学习上也一并受用 在编程方面有着天赋异禀的人毕竟是少数 xff0c 我们大多数人想要从C语言小白进阶到高手
  • C语言实例:3个数从小到大排序

    需求 任意输入3个整数 xff0c 对这3个整数由小到大进行排序 xff0c 并将排序后的结果输出 源码 64 author 冲哥 64 date 2021 5 7 13 37 64 description 实现对这3个整数由小到大进行排序
  • C 预处理指令

    C 预处理指令 C语言 C 语言的预处理器 用于在编译器处理程序之前预扫描源代码 xff0c 完成头文件的包含 宏扩展 条件编译 行控制 xff08 line control xff09 等操作 编译的四个阶段 C语言标准规定 xff0c
  • c语言怎么输入3个数输出最大值

    方法 xff1a 首先使用scanf 接收从键盘输入的三个数 xff1b 然后使用 if else 语句比较三个数的大小 xff0c 获得最大值 xff1b 最后使用print 函数将最大值输出即可 c语言输入3个数输出最大值 includ
  • C 运算符中不能重载的是哪些

    C 运算符中不能重载的有 xff1a 1 条件运算符 xff1b 2 成员访问运算符 xff1b 3 域运算符 xff1b 4 长度运算符 sizeof xff1b 5 成员指针访问运算符 gt 和 重载 xff1a 让操作符可以有新的语义
  • scanf在c语言中的作用是什么?

    scanf 函数 scanf 是C语言中的一个输入函数 与printf函数一样 xff0c 都被声明在头文件stdio h里 xff0c 因此在使用scanf函数时要加上 include xff08 在有一些实现中 xff0c printf
  • C语言中字符串的结束标志是什么

    C语言中字符串的结束标志是 39 0 39 C语言中没有专门的字符串变量 xff0c 通常用一个字符数组来存放一个字符串 xff0c 字符串总是以 39 0 39 作为结束符 39 0 39 就是8位的00000000 xff0c 因为字符
  • Linux驱动开发(十八)---网络(网卡)驱动学习

    前文回顾 Linux驱动开发 xff08 一 xff09 环境搭建与hello world Linux驱动开发 xff08 二 xff09 驱动与设备的分离设计 Linux驱动开发 xff08 三 xff09 设备树 Linux驱动开发 x