Linux网络设备驱动框架

news2024/11/17 2:32:43

1. 网络设备驱动框架

1.1网际协议分层

优点: 便于封装;

在这里插入图片描述

1.2 网络设备驱动程序结构分层

在这里插入图片描述

  • 协议接口层: 向网络协议提供统一的数据包发送接口,上层任何形式的协议都通过dev_queue_xmit()发送,通过netif_rx()接收,都使用sk_buff作为数据的载体;
  • 设备接口层: 向协议接口层提供统一的用于描述具体设备(网络设备属性和操作的结构体struct net_device),这个结构从整体规划了具体操作硬件的设备驱动功能层的结构,是设备驱动功能层的各个函数的容器;
  • 驱动功能层: 编写驱动功能层的相关函数,以填充net_device数据结构的内容,并注册到内核;通过hard_start_xmit ()启动发送,并通过网络设备上的中断触发接收操作,通过中断或POLL机制接收;
  • 设备与媒介层: 完成数据收发的物理实体,网卡被设备驱动层中的函数在物理上驱动;

1.3 网络协议接口层

主要功能: 给上层协议提供透明的数据包发送和接收接口。

1.3.1 发送接口
/* 定义: net/core/dev.c */
int dev_queue_xmit(struct sk_buff *skb)
{
	return __dev_queue_xmit(skb, NULL);
}

/* 声明: include/linux/netdevice.h */
int dev_queue_xmit(struct sk_buff *skb);
1.3.2 接收接口

函数netif_rx是在网上收到数据包后,通过中断机制通知CPU而间接调用的中断处理例程;

/* 定义: net/core/dev.c */
int netif_rx(struct sk_buff *skb)
{
    trace_netif_rx_entry(skb);
    return netif_rx_internal(skb);
}
1.3.3 sk_buff组成

sk_buff: 套接字缓冲区,用于网络子系统各层之间传递数据,主要包括Data buffer ;

Data buffer 由两部分组成:

  • Packet data: 通过网卡收发的报文数据,包括head_room、data、end_room;
  • skb_shared_info: 作为packet data的补充,用于存储ip分片,其中sk_buff *frag_list是一系列子skbuff链表,而frag[]是由一组单独的page组成的数据缓冲区。

在这里插入图片描述

struct sk_buff 结构体:

/* 定义: include/linux/skbuff.h */
struct sk_buff {
        /* sk_buff是双向链表:sk_buff结构都必须能够很快找到链表头节点 */
        struct sk_buff          *next;            // 指向后面的sk_buff结构体的指针
        struct sk_buff          *prev;            // 指向前一个sk_buff结构体的指针 
    
        ktime_t                 tstamp;
        struct sock             *sk;              // 
        struct net_device       *dev;             // 对应的net_device
        char                    cb[48] __aligned(8);
        unsigned long           _skb_refdst;
        unsigned int            len,              // 表示数据区的长度(tail-data)与分片结构体数据区的长度之和
                                data_len;         // 只表示分片结构体数据区的长度,所以len=(tail - data) + data_len
        __u16                   mac_len,          // mac报头的长度
                                hdr_len;
        __be16                  protocol;         // 包的协议类型,标识是IP包还是ARP包还是其他数据包
        __u16                   inner_transport_header;
        __u16                   inner_network_header;
        __u16                   inner_mac_header;
        __u16                   transport_header; // 指向传输包头
        __u16                   network_header;   // 指向传输层包头
        __u16                   mac_header;       // 指向链路层包头
    
        /* These elements must be at the end, see alloc_skb() for details.  */
        sk_buff_data_t          tail;             // 指向当前数据包的尾地址, 随着各个网络层的加工而变化
        sk_buff_data_t          end;              // 数据缓冲区的结束地址
        unsigned char           *head,            // 数据缓冲区的开始地址
                                *data;            // data指向当前数据包的首地址, 随着各个网路层的加工而变化
		... ...
};

在这里插入图片描述

/* sk_buff链表的表头 */
struct sk_buff_head {
      /* These two members must be first. */
      struct sk_buff *next;  // 链表的下一个元素
      struct sk_buff *prev;  // 链表的上一个元素
      __u32           qlen;  // sk_buff结构的数量
      spinlock_t      lock;  // 自旋锁,防止对表的并发访问
};

sk_buff管理操作相关函数:

在这里插入图片描述

/* 向skb尾部添加数据 */
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
    unsigned char *tmp = skb_tail_pointer(skb); /* 获取当前skb->tail */
    
    SKB_LINEAR_ASSERT(skb);        /* 要求skb数据区必须为线性 */
    skb->tail += len;              /* skb尾部增加len字节 */
    skb->len  += len;              /* skb数据总长度增加len字节 */

    /* 如果增加之后的tail > end ,则打印错误信息并进入oops */
    if (unlikely(skb->tail > skb->end))
        skb_over_panic(skb, len, __builtin_return_address(0));
    
    return tmp;  /* 返回添加数据的第一个字节位置 */
}

在这里插入图片描述

/* 向skb数据区头部添加数据 */
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
    skb->data -= len;      /* 数据区data指针前移len字节 */
    skb->len  += len;      /* 数据总长度增加len字节 */

    /* 如果增加之后的tail > end ,则打印错误信息并进入oops */
    if (unlikely(skb->data<skb->head))
        skb_under_panic(skb, len, __builtin_return_address(0));

    return skb->data;     /* 返回新的data指针 */
}

在这里插入图片描述

/* 从数据区头部移除数据 */
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
{
    /* 根据移除数据长度判断函数调用 */
    return skb_pull_inline(skb, len);
}

/* 根据移除数据长度判断函数调用 */
static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len)
{
    /* 移除长度 > skb数据总长度,返回NULL; 否则,继续调用_skb_pull函数 */
    return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
}

/* 从skb数据区头部移除数据 */
static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
    skb->len -= len;                  /* 数据总长度减去len字节 */
    BUG_ON(skb->len < skb->data_len); /* 数据总长度是否有异常 */

    /* data指针移动len字节, 返回移除之后新的data指针 */
    return skb->data += len;
}

在这里插入图片描述

/* 创建头空间,只能对空skb使用 */
static inline void skb_reserve(struct sk_buff *skb, int len)
{
    skb->data += len;    /* 数据区data指针增加len字节*/
    skb->tail += len;    /* 数据区tail指针增加len字节 */
}

sk_buff缓冲区分配函数:

在这里插入图片描述

在分配一个缓冲区时,需要分配两块内存(一个是缓冲区,一个是缓冲区的描述结构sk_buff)。

alloc_skb函数分为三部分: ①、从cache中分配内存; ②、初始化分配的skb的相关域; ③、处理fclone

/* 分配一个数据长度为size的network buffer(skb+data_buffer)(net/core/skbuff.c)*/
static inline struct sk_buff *alloc_skb(unsigned int size, gfp_t priority)
{
    return __alloc_skb(size, priority, 0, NUMA_NO_NODE);
}

/*******************************************************************************
 输入参数: size:skb的数据大小
          gfp_mask:不用解释
          fclone:表示从哪个cache中分配 fclone=1:从skbuff_fclone_cache上分配
                                      fclone=0:从skbuff_head_cache上分配 
*******************************************************************************/
struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, int flags, int node)
{
   struct kmem_cache *cache;
   struct skb_shared_info *shinfo;
   struct sk_buff *skb;
   u8 *data;
   bool pfmemalloc;

   /* 通过flags的值判断是从fclone_cache还是head_cache中分配 */
   cache = (flags & SKB_ALLOC_FCLONE) ? skbuff_fclone_cache : skbuff_head_cache;

   /* 分配skb */
   skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
   if (!skb)  goto out;

   /* 数据对齐:按一级缓存的大小对齐 */
   size = SKB_DATA_ALIGN(size);

   /* 对齐后的数据加上skb_shared_info对齐后的大小 */
   size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));

   /* 分配数据区:大小为size * sizeof(struct skb_shared_info)的大小 */ 
   data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);
   if (!data)  goto nodata;

   /* 除了skb_shared_info以外的数据大小 */
   size = SKB_WITH_OVERHEAD(ksize(data));
   prefetchw(data + size);

   /* 初始化相关域 */
   memset(skb, , offsetof(struct sk_buff, tail));

   /* 总长度 = skb + data + skb_shared_info */
   skb->truesize = SKB_TRUESIZE(size);

   /* PFMEMALLOC分配标记 */
   skb->pfmemalloc = pfmemalloc;

   /* 设置引用计数为1 */
   atomic_set(&skb->users, 1);

   /*head data tail均指向数据区头部*/
   skb->head = data;
   skb->data = data;
   skb_reset_tail_pointer(skb);

   /* end指向数据区尾部 */
   skb->end = skb->tail + size;

   /* 初始化默认各层header偏移值 */
   skb->mac_header = (typeof(skb->mac_header))~0U;
   skb->transport_header = (typeof(skb->transport_header))~0U;
   
   /* 从end开始的区域为skb_shared_info */
   shinfo = skb_shinfo(skb);
   memset(shinfo, , offsetof(struct skb_shared_info, dataref));

   /* 设置引用计数为1 */
   atomic_set(&shinfo->dataref, );
   kmemcheck_annotate_variable(shinfo->destructor_arg);

   /* 如果flags==1,则需要多配一块内存,因此需要设置对应的fclone域(fclone_cache) */
   if (flags & SKB_ALLOC_FCLONE) 
   {
       struct sk_buff_fclones *fclones;
       /* 得到clone结构 */
        fclones = container_of(skb, struct sk_buff_fclones, skb1);
        kmemcheck_annotate_bitfield(&fclones->skb2, flags1);
        skb->fclone = SKB_FCLONE_ORIG;             /* 设置克隆标记 */
        atomic_set(&fclones->fclone_ref, 1);       /* 设置引用为1 */
        fclones->skb2.fclone = SKB_FCLONE_CLONE;   /* 设置skb2的克隆标记 */
    }

out:
    return skb;
nodata:
    kmem_cache_free(cache, skb);

    skb = NULL;
    goto out;
}

sk_buff缓冲区释放函数:

 /* 释放skb: kfree_skb用于失败时丢包释放 */
void kfree_skb(struct sk_buff *skb)
{
    if (unlikely(!skb))
        return;

    /* 引用为1,可直接释放 */
    if (likely(atomic_read(&skb->users) == 1))
	{
        smp_rmb();
	}
    else if (likely(!atomic_dec_and_test(&skb->users))) /* 对引用减1,并且判断,如果结果不为0,说明还有引用,返回 */
	{
        return;
	}
	
    trace_kfree_skb(skb, __builtin_return_address());

    //真正的skb释放
    __kfree_skb(skb);
}

/* 正常释放skb */
void consume_skb(struct sk_buff *skb)
{
    if (unlikely(!skb))
        return;

    if (likely(atomic_read(&skb->users) == 1))
	{
        smp_rmb();
    }
	else if (likely(!atomic_dec_and_test(&skb->users)))
	{
        return;
	}
	
    trace_consume_skb(skb);
    
    __kfree_skb(skb);
}

#define dev_kfree_skb(a)    consume_skb(a)

/* 释放skb */
void __kfree_skb(struct sk_buff *skb)
{
    skb_release_all(skb);  /* 释放skb附带的所有数据 */
    kfree_skbmem(skb);     /* 释放skb */
}

(2)struct net_device

Linux内核中使用net_device来描述一个网络设备,net_device是设备接口层的核心, 也是编写网络驱动核心的对象。

struct net_device {
    char                    name[IFNAMSIZ]; // 网络设备的名称, 网络设备被载入后会出现在ifconfig中, 比如默认的eth0就是这个
	unsigned long           mem_end;        // 网络设备所使用的共享内存起始地址
	unsigned long           mem_start;      // 网络设备所使用的共享内存结束地址
	unsigned long           base_addr;      // 表示网络设备的IO基地址
	int                     irq;            // 设备使用的中断号
	unsigned long           state;
	
	struct list_head        dev_list;
	struct list_head        napi_list;
	struct list_head        unreg_list;
	struct list_head        close_list;
	netdev_features_t       features;       // 用户层可以修改的特征
	netdev_features_t       hw_features;    // 用户层不能修改的特征
	netdev_features_t       wanted_features;
	const struct net_device_ops *netdev_ops;// 网络设备的操作方法集
	const struct ethtool_ops *ethtool_ops;  // ethtool的方法集
	const struct forwarding_accel_ops *fwd_ops;
	const struct header_ops *header_ops;    // 协议头操作集
	unsigned int            flags;          /* interface flags (a la BSD)   */
	unsigned int            priv_flags; /* Like 'flags' but invisible to userspace.
										* See if.h for definitions. */
	unsigned short          gflags;                   
	unsigned short          padded; /* How much padding added by alloc_netdev() */
	unsigned char           operstate; /* RFC2863 operstate */
	unsigned char           link_mode; /* mapping policy to operstate */
	unsigned char           if_port;        /* Selectable AUI, TP,..*/
	unsigned char           dma;            /* DMA channel          */
	unsigned int            mtu;    /* interface MTU value          */
	unsigned short          type;   /* interface hardware type      */
	unsigned short          hard_header_len;        /* hardware hdr length  */
	unsigned short          needed_headroom;
	unsigned short          needed_tailroom;
	unsigned char           perm_addr[MAX_ADDR_LEN]; /* permanent hw address */
	unsigned char           addr_assign_type; /* hw address assignment type */
	unsigned char           addr_len;       /* hardware address length      */
	struct kset             *queues_kset;
	int                     watchdog_timeo; /* used by dev_watchdog() */
};

(3)struct net_device_ops

网络设备的操作方法集。

struct net_device_ops {
	int          (*ndo_init)(struct net_device *dev);
	void         (*ndo_uninit)(struct net_device *dev);
	
	/* 打开/关闭网络接口设备,获得设备需要的I/O地址、IRQ、DMA通道等 */
	int          (*ndo_open)(struct net_device *dev);
	int          (*ndo_stop)(struct net_device *dev);
    
    /* 启动数据包的发送,当系统调用驱动程序的xmit函数时,需要向其传入一个sk_buff结构体指针,
       以使得驱动程序能获取从上层传递下来的数据包 */
	netdev_tx_t  (*ndo_start_xmit) (struct sk_buff *skb,struct net_device *dev);
	void         (*ndo_change_rx_flags)(struct net_device *dev,int flags);
	void         (*ndo_set_rx_mode)(struct net_device *dev);
    
    /* 用于设置设备的MAC地址 */
	int          (*ndo_set_mac_address)(struct net_device *dev,void *addr);
	int          (*ndo_validate_addr)(struct net_device *dev);
    
    /* 用于进行设备特定的I/O控制 */
	int          (*ndo_do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
    /* 用于配置接口,也可用于改变设备的I/O地址和中断号 */
	int          (*ndo_set_config)(struct net_device *dev, struct ifmap *map);
	int          (*ndo_change_mtu)(struct net_device *dev, int new_mtu);
	int          (*ndo_neigh_setup)(struct net_device *dev,struct neigh_parms *);
    
    /* 当数据包的发送超时时,ndo_tx_timeout()函数会被调用,
      该函数需采取重新启动数据包发送过程或重新启动硬件等措施来恢复网络设备到正常状态 */
	void         (*ndo_tx_timeout) (struct net_device *dev);
};

相关API

(1)分配/释放net_device

/*******************************************************************************
 功能描述: 分配及初始化net_device对象(linux/etherdevice.h)
 输入参数: sizeof_priv:私有数据大小(单位:字节数)
 输出参数:
 返 回 值: 失败:NULL    成功:net_device对象的首地址
*******************************************************************************/
struct net_device *alloc_etherdev(int sizeof_priv);

/*******************************************************************************
 功能描述: 分配及初始化net_device对象(linux/netdevice.h)
 输入参数: sizeof_priv:私有数据大小(单位:字节数)
          name:物理接口名("名称%d")
          name_assign_type:NET_NAME_UNKNOWN
          setup:初始化函数
 输出参数:
 返 回 值: 失败:NULL    成功:net_device对象的首地址
*******************************************************************************/
struct net_device *alloc_netdev(int sizeof_priv, const char *name,
                                unsigned char name_assign_type, void (*setup)(struct net_device *));

/* 释放 */
void free_netdev(struct net_device *dev);

(2)以太网的初始化

在初始化一个以太网设备的时候应该被调用,主要作用是针对以太网标准对net_device对象进行初始化。

void ether_setup(struct net_device *dev);

(3)注册/注销net_device

int register_netdev(struct net_device *dev);    /* 注册 */
void unregister_netdev(struct net_device *dev); /* 注销 */

(4)开始/停止发送队列

void netif_start_queue(struct net_device *dev); /* 开启发送队列 */
void netif_stop_queue(struct net_device *dev);  /* 停止发送队列 */

4. 网络设备的中断处理函数

中断处理函数

是网络设备媒介层相设备驱动功能层发送数据的接口, 网卡接收到数据是通过中断的方式上报的, 所以网络驱动中的中断处理函数就是第一时间队接收到的数据进行处理的地方,这个函数最终一定要调用netif_rx()将收到的数据上报到协议接口层。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/192657.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一种用于IDC机房数据挖掘的应用实现

&#xff08;作者单位&#xff1a;华北石油通信有限公司&#xff09;摘要&#xff1a;介绍了适用于数据中心可预定义、自定义场景的轻量级应用实现。现实中监测系统的数据大多沉淀在数据库中&#xff0c;且获取不同设备的信号数据并把这些数据展示出来&#xff0c;多受检测系统…

LabVIEW更高的吞吐量与更少的延迟A

LabVIEW更高的吞吐量与更少的延迟1在设计系统时&#xff0c;“速度”有两个含义。“需要多快采集样品&#xff1f;”通常转化为吞吐量。“样本后需要多快获得结果&#xff1f;”通常转化为延迟。在大多数测量或控制应用中&#xff0c;目标是将真实世界的数据从信号中获取到某种…

LeetCode哈希表相关解法

哈希表1. 理论哈希碰撞的解决方法拉链法线性探测法2. 有效的字母异位词[242. 有效的字母异位词](https://leetcode.cn/problems/valid-anagram/)3. 两个数组的交集[349. 两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/)4. 快乐数[202. 快乐数](htt…

16_tomcat

tomcat 一、jsp一句话木马 这个东西网上百度就有 <%!class U extends ClassLoader {U(ClassLoader c) {super(c);}public Class g(byte[] b) {return super.defineClass(b, 0, b.length);}}public byte[] base64Decode(String str) throws Exception {try {Class clazz …

Coresight - HW Assisted Tracing on ARM

文章目录一、Introduction二、Acronyms and Classification2.1 Acronyms2.2 Classification三、Device Tree Bindings四、Framework and implementation五、Device Naming scheme六、Topology Representation七、How to use the tracer modules7.1 Using the sysFS interface7.…

如何实现RTMP协议

认识rtmp rtmp是Adobe公司出品的流媒体传输协议&#xff0c;它的全称是Real Time Messaging Protocol&#xff0c;是一个实时消息传输协议&#xff0c;学习RTMP一定要抓住 一个关键点&#xff1a;消息。 rtmp协议的原文可以在Adobe官网下载&#xff0c;内容十分精简&#xff…

用户身份管理(CIAM)如何帮助业务持续增长?|身份云研究院

精明的决策者很早就意识到&#xff0c;数字化转型的核心是为用户提供完善的“数字旅程”&#xff0c;这里的用户包括“员工”和“客户”&#xff0c;而“数字旅程”的核心则是持续提供优质的「数字用户体验&#xff08;DCX&#xff09;」。本文将主要探讨如何制定完善“客户数字…

window版Docker打包镜像并上传到服务器使用

背景&#xff1a;利用jmeter实现自动化进行线上监视&#xff0c;要部署于多台服务器上监视&#xff0c;为了节省时间&#xff0c;方便使用&#xff0c;最终决定使用docker将自动化脚本打包成镜像&#xff0c;这样只要服务器上安装docker环境&#xff0c;直接下载镜像就可以使用…

2023全新SF授权系统源码 V3.7全开源无加密版本

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示三、学习资料下载一、详细介绍 SF多应用综合验证授权系统 V4.0更新内容 采用ThinkPHP 6.0 EasyWebAdmin 支持自定义判断规则&#xff08;默认提供域名QQ机器码规则&#xff09; 支持在线充值&#xff0c;用户Api授权&…

(免费分享)springboot人事管理系统

基础环境&#xff1a;1. JDK:1.82. MySQL:5.73. Maven3.01. 核心框架&#xff1a;Spring Boot 2.2.13.RELEASE2. ORM框架&#xff1a;MyBatisPlus 3.1.23. 数据库连接池&#xff1a;Druid 1.2.84. 安全框架&#xff1a;Apache Shiro 1.8.05. 日志&#xff1a;SLF4J &#xff0c…

最近邻插值法

文章目录前言一、最近邻插值法二、代码实现总结本章节进入图像处理&#xff0c;利用python语言来实现各种图像处理的方法&#xff0c;从软件角度去理解图像处理方法&#xff0c;为后期的FPGA处理图像做准备。 前言 一、最近邻插值法 最近邻插值就是在目标像素点上插入离对应原…

界面控件DevExpress WinForm中文教程 - 如何应用Windows 11 UI?

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

全网最详细的org.springframework.jdbc.UncategorizedSQLException的多种解决方法

文章目录1. 引出问题2. 分析问题3. 解决问题4. 解决该问题的其他方法4.1 方法14.2 方法24.3 方法34.4 方法4如果你遇到的问题不是我所遇到的问题&#xff0c;可以使用最下面的方法解决你遇到的这个错误。 1. 引出问题 今天在写“Mybatis-Plus中分页插件PaginationInterceptor…

利用Python读取外部数据文件

名字&#xff1a;阿玥的小东东 学习&#xff1a;python、c 主页&#xff1a;阿玥的小东东 目录 一、读取文本文件的数据 二、读取电子表格文件 三、读取统计软件生成的数据文件 不论是数据分析&#xff0c;数据可视化&#xff0c;还是数据挖掘&#xff0c;一切的一切全都是以…

java常用类: Arrays类的常用方法

java常用类型: Ineteger等包装类 String类&#xff0c;StringBuffer类和StringBuilder类 Math类及常用方法 System类及常用方法 Arrays类及常用方法 BigInteger类和BigDecimal类及常用方法 日期类Date类,Calender类和LocalDateTime类 文章目录ArraysArrays常用方法Arrays.sort(…

全排列问题的解题思路

假设有这么个正整数n&#xff0c;要求输出1到n的所有排列&#xff1f;   输入&#xff1a;3 输出&#xff1a;123&#xff0c;132&#xff0c;213&#xff0c;231&#xff0c;312&#xff0c;321 一、无脑循环求解&#xff1f; 拿到这个问题&#xff0c;当然我的第一个想法就…

上下文驱动的图上文案生成

✍&#x1f3fb; 本文作者&#xff1a;持信、弈臻、悟放、积流、孟诸1. 摘要为商品图片上特定位置配上装饰性文案来突出重点在广告业务中有着十分广泛的应用前景。然而&#xff0c;现有的图片文案描述生成系统均生成与图片位置关系无关的文案&#xff0c;无法很好地应用到广告业…

66. Python 类的总结

66. 类的总结 文章目录66. 类的总结1. 类2. 对象3. 类的语法4. 属性5. 方法6. 创建对象7. 调用属性8. 调用方法9. 方法的值的传递第1情况&#xff1a;没有值第2种情况&#xff1a;有值10. __init__方法1. __init__写法2. __init__作用3. 重点关注11. 自定义函数和方法的异同1. …

Unity渲染管线(Render Pipeline)笔记

Rendering是什么 渲染Rendering可以理解为将拿到的3D数据生成一副2D图像的过程。 这些3D数据包含&#xff1a;3D模型本身的点的信息&#xff0c;三角形面的描述信息&#xff0c;模型应用的材质以及摆放的虚拟相机的信息等。Rendering过程会使用全部的数据&#xff08;物体的几何…

摄像头录像大师推荐?如何录制摄像头,图文教程

现如今&#xff0c;很多笔记本电脑上都会携带摄像头&#xff0c;用来录制摄像头画面&#xff0c;方便小伙伴的时候。可很多小伙伴却表示&#xff0c;自己不知道电脑摄像头画面该如何录制。有什么有什么好用的摄像头录制大师&#xff1f;如何录制摄像头画面&#xff1f;本篇文章…