嵌入式驱动学习第三周——linux内核链表

news2024/12/28 3:50:53

前言

   在 Linux 内核中使用最多的数据结构就是链表了,其中就包含了许多高级思想。 比如面向对象、类似C++模板的实现、堆和栈的实现。

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

目录

  • 前言
  • 1. 链表简介
    • 1.1 单链表
    • 1.2 双链表
    • 1.3 循环链表
  • 2. 内核链表
    • 2.1 list_head
    • 2.2 链表初始化
    • 2.3 增删改查
      • 2.3.1 添加节点
      • 2.3.2 删除节点
      • 2.3.3 遍历操作
    • 2.4 搬移
    • 2.5 其他
  • 3. 实际案例
    • 3.1 定义自己的结构体
    • 3.2 初始化链表
    • 3.3 增删改查函数定义
    • 3.4 调用
    • 3.5 完整代码
  • 参考资料

1. 链表简介

   链表作为常用的数据结构,是使用指针将一系列数据连成一条链,是一种线性表,通常链表数据结构至少应包括两个域,数据域和指针域。数据域用于存储数据,指针域用于建立与下一个节点的联系。相信其特性也不需要过多介绍了,大部分人应该都知道的,这部分就简单过一遍

1.1 单链表

   单链表,其仅有一个指针域指向后继节点,对单链表只能从头到顺序访问

在这里插入图片描述

1.2 双链表

   双链表多了一个指针域,又后继和前驱,既能从头到尾,又能从尾到头访问

在这里插入图片描述

1.3 循环链表

   循环链表是在单链表与双链表的基础上,让最后一个节点的指针域指向最开始的节点,双链表的话第一个节点的前驱也要指向最后一个节点。好处是可以从任意一个节点开始访问完全部的节点。

在这里插入图片描述

2. 内核链表

   在Linux内核中使用了大量的链表结构来组织数据,包括设备列表以及各种功能模块中的数据组织。这些链表大多采用在include/linux/list.h实现的一个相当精彩的链表数据结构。事实上,内核链表就是采用双循环链表机制,支持FIFO(先进先出)和LIFO(后进先出)操作。

   通过使用循环链表,内核开发者可以在保持代码量最小的情况下实现多种数据结构和算法,提高了内核的性能和可维护性。此外,循环双链表还具有高效地插入和删除节点的优点,在内核开发中很重要,因为内核需要频繁地对数据结构进行修改和操作。

   内核链表有别于传统链表就在节点本身不包含数据域,只包含指针域。故而可以很灵活的拓展数据结构。

2.1 list_head

   这个结构自身意义不大,但是在内核链表中,起着衔接的作用,可以说是内核链表的核心

struct list_head {
   struct list_head *next, *prev;
};

2.2 链表初始化

   初始化分为宏初始化和借口初始化

   宏初始化主要有LIST_HEAD_HEAD_INITLIST_HEAD

LIST_HEAD_HEAD_INIT 宏,对于任意给定的结构指针,将【前驱】和【后继】指针都指向自己,作为链表头指针
LIST_HEAD 本质就是赋予了 name有list_head属性,是基于LIST_HEAD_HEAD_INIT 宏的视线

#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
   struct list_head name = LIST_HEAD_INIT(name)

   接口初始化比较直白,INIT_LIST_HEAD和宏实现意图相同,直接将链表头指针的前驱和后继都指向自己,定义如下:

static inline void INIT_LIST_HEAD(struct list_head *list)
{
   list->next = list;
   list->prev = list;
}

   要使用list_head的话,就需要创建一个宿主结构,让其包含list_head

typedef struct my_list {
	int val;
	struct list_head list;
} My_List;

   然后我们根据这个结构体创建一个节点:

My_List first_node = {
	.val = 10,
	.list = LIST_HEAD_INIT(first_node.list);	// 将first_node.list的前驱和后继均指向自己

   即如下所示:

在这里插入图片描述

   此处list_node高能的地方又来了,我们知道container_of宏可以根据成员查找到结构体,那么通过结合container_of宏就可以通过list_node成员访问到结构体,然后再根据结构体访问其中的数值

   container_of文章地址:嵌入式驱动学习第三周——container_of()宏

2.3 增删改查

2.3.1 添加节点

   添加节点使用的是list_add函数,其是头部插入,即总是在链表的头部插入,链表是向左生长的。其底层调用的是__list_add函数。

/* 
 * @description: 添加内核链表节点,头插法
 * @param-new  : 要添加的新节点
 * @param-head : 要加入链表的头节点
 * @return     : 无
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
   __list_add(new, head, head->next);
}

static inline void __list_add(struct list_head *new,
         struct list_head *prev,
         struct list_head *next)
{
   next->prev = new;
   new->next = next;
   new->prev = prev;
   prev->next = new;
}

   举例说明,首先可以创建一个头节点

LIST_HEAD(listHead);

在这里插入图片描述

   然后用2.2中的方法创建第一个节点

struct my_data_list first_data =
{ 
    .val = 1,
    .list = LIST_HEAD_INIT(first_data.list),
};

   创建完毕后用list_add函数将其加入到头结点中

list_add(&frist_data.list, &listHead);

在这里插入图片描述

   然后创建第二个节点并添加进去,注意看第二个节点添加的位置,是在定义的头结点之后,即头部插入

struct my_data_list second_data =
{ 
    .val = 2,
    /* 也可以调用接口 初始化*/
    .list = LIST_HEAD_INIT(second_data.list),
};
 
list_add(&second_data.list, &listHead);

在这里插入图片描述

   另一种是使用尾插法,即在节点尾部添加节点,函数为list_add_tail函数,其也是调用的__list_add函数,但是调用的时候二三两个参数位置反了一下:

/* 
 * @description: 添加内核链表节点,尾插法
 * @param-new  : 要添加的新节点
 * @param-head : 要加入链表的头节点
 * @return     : 无
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
   __list_add(new, head->next, head);
}

   前面操作一样,来看插入第二个节点时的操作,注意看第二个节点插入的位置,是在第一个节点之后

struct my_data_list second_data =
{ 
    .val = 2,
    /* 也可以调用接口 初始化*/
    .list = LIST_HEAD_INIT(second_data.list),
};
 
list_add_tail(&second_data.list, &listHead);

在这里插入图片描述

2.3.2 删除节点

   删除节点采用list_del函数或list_del_init函数

/* 
 * @description: 删除指定节点,并将节点指向特定位置
 * @param-entry: 要删除的节点
 * @return     : 无
 */
static inline void list_del(struct list_head *entry)
/* 
 * @description: 删除指定节点,删除后将节点置为空链状态,即前驱和后继均指向自己
 * @param-entry: 要删除的节点
 * @return     : 无
 */
static inline void list_del_init(struct list_head *entry)

2.3.3 遍历操作

   遍历也是一个相当重要也是相当常用的操作,首先需要介绍一下list_entry宏,list_entry宏本质是container_of宏:

#define list_entry(ptr, type, member) \
   container_of(ptr, type, member)

   其使用方法和container_of是一致的:

list_entry(&my_list_data.list, typeof(&my_list_data), list)

   既然是双向链表,那肯定就可以正向遍历,也可以反向遍历。

   list_for_each 它实际上是一个for循环,利用传入的pos作为循环变量,从表头head开始,逐项向后(next方向)移动pos,直至又回到head

/* 
 * @description: 正向遍历双向链表
 * @param-pos  : 一个list_node指针,作为遍历指针
 * @param-head : 要遍历链表的头结点
 * @return     : 无
 */
#define list_for_each(pos, head) \
 for (pos = (head)->next; pos != (head); pos = pos->next)

   其使用方法如下:

struct my_data_list* p;
struct list_head *pos; //pos指向每一个大结构体中的小结构体
list_for_each(pos, &(Head->list)) //循环遍历
{
    //以上循环只提供小结构体指针的遍历,但是遍历找的是大结构体的数据域,根据每个pos得到大结构体地址
    p = list_entry(pos, Node, list);
    printk(p->data);  //打印 
}

   list_for_each_entry 是遍历每一个list,然后获取宿主的结构地址:

/* 
 * @description : 正向遍历双向链表并获取宿主的结构地址
 * @param-pos   : 一个自己定义结构的指针,循环体内可以直接调用自己的成员变量
 * @param-head  : 要遍历链表的头结点
 * @param-member: 自己结构体中list_head的名称
 * @return      : 无
 */
#define list_for_each_entry(pos, head, member)    \
 for (pos = list_entry((head)->next, typeof(*pos), member); \
      &pos->member != (head);  \
      pos = list_entry(pos->member.next, typeof(*pos), member))

   使用案例:

struct my_data_list* p;
list_for_each_entry(p, &(Head->list), list) //循环遍历,最后一个参数是list是因为我结构体中list_head成员名字叫list
{
    printk(pos->val);  //打印
}

   list_for_each_prev是反向遍历,用法同list_for_each

/* 
 * @description: 反向遍历双向链表
 * @param-pos  : 一个list_node指针,作为遍历指针
 * @param-head : 要遍历链表的头结点
 * @return     : 无
 */
#define list_for_each_prev(pos, head) \
 for (pos = (head)->prev; pos != (head); pos = pos->prev)

   list_for_each_entry_reverse是反向遍历,用法同list_for_each_entry

/* 
 * @description : 正向遍历双向链表并获取宿主的结构地址
 * @param-pos   : 一个自己定义结构的指针,循环体内可以直接调用自己的成员变量
 * @param-head  : 要遍历链表的头结点
 * @param-member: 自己结构体中list_head的名称
 * @return      : 无
 */
#define list_for_each_entry_reverse(pos, head, member)   \
   for (pos = list_entry((head)->prev, typeof(*pos), member); \
        &pos->member != (head);  \
        pos = list_entry(pos->member.prev, typeof(*pos), member))

2.4 搬移

   内核提供了将原本属于一个链表的节点移动到另一个链表的操作,并根据插入到新链表的位置分为两类:头部搬移和尾部搬移。搬移的本质就是删除加插入。

/* 
 * @description: 从一个链表删除并添加到另一个链表中,头插法添加
 * @param-list : 要移动的节点
 * @param-head : 要添加进的链表的头结点
 * @return     : 无
 */
static inline void list_move(struct list_head *list, struct list_head *head)
/* 
 * @description: 从一个链表删除并添加到另一个链表中,尾插法添加
 * @param-list : 要移动的节点
 * @param-head : 要添加进的链表的头结点
 * @return     : 无
 */
static inline void list_move_tail(struct list_head *list, struct list_head *head)

2.5 其他

   还有其他操作比如合并,替换等,详情可见参考资料[1]。

3. 实际案例

3.1 定义自己的结构体

   定义一个工程师类,然后定义一个公司类,把工程师放进去,然后最外层定义一个node,数据域就是公司,指针域就是list_head变量。

// 一个公司类
typedef struct company_Struct {
    char name[100];
    int company_val;
    int employee_count;     // 员工人数
} Company;

typedef struct my_node {
    // 数据域
    Company company;

    // 指针域
    struct list_head list;
}Node, *pNode;

3.2 初始化链表

   之后初始化一个链表,即构建一个头节点。

// 链表初始化,构建一个头节点
pNode List_Init(void) {
    pNode Head = (pNode)kmalloc(sizeof(Node), GFP_ATOMIC);
    if (Head == NULL) {
        printk("malloc failed!");
        return NULL;
    }

    INIT_LIST_HEAD(&Head->list);

    return Head;
}

3.3 增删改查函数定义

   之后写一些增加节点,删除节点,遍历节点的函数。

   增加节点,首先先创建一个节点,然后再添加进去。

// 增加节点函数
int Node_Insert(pNode head, Company elem)
{
    pNode newNode = (pNode)kmalloc(sizeof(Node), GFP_ATOMIC);
    if (newNode == NULL) {
        printk("malloc failed!");
        return -1;
    }

    newNode->company = elem;

    list_add(&(newNode->list), &(head->list));

    return 0;
}

   删除节点,在调用list_del函数后,要用free释放节点

void Node_Delete(pNode delNode) {
    list_del(&(delNode->list));
    printk("成功删除\r\n");
    kfree(delNode);
    printk("成功释放\r\n");
}

   遍历函数

// 遍历节点
void List_Traversal(pNode head) {
    pNode p;
    struct list_head *pos;
    list_for_each(pos, &(head->list)) {
        p = list_entry(pos, Node, list);
        printk("%s\t%d\t%d\r\n", p->company.name, p->company.company_val, p->company.employee_count);
    }
}

3.4 调用

   创建三家公司com1叫aaaa,com2叫bbbb,com3叫cccc。然后都添加进链表,最后删除公司2。

Company com1 = {"aaaa", 100, 10};
Company com2 = {"bbbb", 200, 8};
Company com3 = {"cccc", 300, 40};

pNode head = List_Init();        // 初始化一个列表
Node_Insert(head, com1);
Node_Insert(head, com2);
Node_Insert(head, com3);

printk("+++++++所有的节点+++++++");
List_Traversal(head);

Node_Delete(list_entry(head->list.next->next, Node, list));
printk("+++++++删除部分节点+++++++");
List_Traversal(head);

3.5 完整代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/of_device.h>
#include <linux/list.h>
#include <linux/slab.h>

#define chrdevTest_CNT      1
#define chrdevTest_NAME     "chrdevTest"

// 一个公司类
typedef struct company_Struct {
    char name[100];
    int company_val;
    int employee_count;     // 员工人数
} Company;

typedef struct my_node {
    // 数据域
    Company company;

    // 指针域
    struct list_head list;
}Node, *pNode;

// 设备结构体
struct chrdevTest_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
};

struct chrdevTest_dev chrdevTest;   // 字符设备

// 链表初始化,构建一个头节点
pNode List_Init(void) {
    pNode Head = (pNode)kmalloc(sizeof(Node), GFP_ATOMIC);
    if (Head == NULL) {
        printk("malloc failed!");
        return NULL;
    }

    INIT_LIST_HEAD(&Head->list);

    return Head;
}

// 增加节点函数
int Node_Insert(pNode head, Company elem)
{
    pNode newNode = (pNode)kmalloc(sizeof(Node), GFP_ATOMIC);
    if (newNode == NULL) {
        printk("malloc failed!");
        return -1;
    }

    newNode->company = elem;

    list_add(&(newNode->list), &(head->list));

    return 0;
}

void Node_Delete(pNode delNode) {
    list_del(&(delNode->list));
    printk("成功删除\r\n");
    kfree(delNode);
    printk("成功释放\r\n");
}

// 遍历节点
void List_Traversal(pNode head) {
    pNode p;
    struct list_head *pos;
    list_for_each(pos, &(head->list)) {
        p = list_entry(pos, Node, list);
        printk("%s\t%d\t%d\r\n", p->company.name, p->company.company_val, p->company.employee_count);
    }
}

static int chrdevTest_open(struct inode *inode, struct file *filp) {
    filp->private_data = &chrdevTest;
    return 0;
}

static ssize_t chrdevTest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
    return 0;
}

static ssize_t chrdevTest_write(struct file *filp, const char __user* buf, size_t cnt, loff_t* offt) {
    return 0;
}

static ssize_t chrdevTest_release(struct inode* inode, struct file* filp) {
    return 0;
}

static struct file_operations chrdevTest_fops = {
    .owner = THIS_MODULE,
    .open = chrdevTest_open,
    .read = chrdevTest_read,
    .write = chrdevTest_write,
    .release = chrdevTest_release,
};

// 入口函数
static int __init containerTest_init(void)
{
    Company com1 = {"aaaa", 100, 10};
    Company com2 = {"bbbb", 200, 8};
    Company com3 = {"cccc", 300, 40};

    pNode head = List_Init();        // 初始化一个列表
    Node_Insert(head, com1);
    Node_Insert(head, com2);
    Node_Insert(head, com3);

    printk("+++++++所有的节点+++++++\r\n");
    List_Traversal(head);

    Node_Delete(list_entry(head->list.next->next, Node, list));
    printk("+++++++删除部分节点+++++++\r\n");
    List_Traversal(head);

    if (chrdevTest.major) {
        chrdevTest.devid = MKDEV(chrdevTest.major, 0);
        register_chrdev_region(chrdevTest.devid, chrdevTest_CNT, chrdevTest_NAME);
    } else {
        alloc_chrdev_region(&chrdevTest.devid, 0, chrdevTest_CNT, chrdevTest_NAME);     // 申请设备号
        chrdevTest.major = MAJOR(chrdevTest.devid);     // 获取主设备号
        chrdevTest.minor = MINOR(chrdevTest.devid);     // 获取次设备号
    }
    printk("chrdevTest major=%d, minor=%d\r\n", chrdevTest.major, chrdevTest.minor);

    // cdev
    chrdevTest.cdev.owner = THIS_MODULE;
    cdev_init(&chrdevTest.cdev, &chrdevTest_fops);

    // 添加cdev
    cdev_add(&chrdevTest.cdev, chrdevTest.devid, chrdevTest_CNT);

    // 创建类
    chrdevTest.class = class_create(THIS_MODULE, chrdevTest_NAME);
    if (IS_ERR(chrdevTest.class)) {
        return PTR_ERR(chrdevTest.class);
    }

    // 创建设备
    chrdevTest.device = device_create(chrdevTest.class, NULL, chrdevTest.devid, NULL, chrdevTest_NAME);
    if (IS_ERR(chrdevTest.device)) {
        return PTR_ERR(chrdevTest.device);
    }

    return 0;
}

// 出口函数
static void __exit containerTest_exit(void)
{
    printk("this is exit function\r\n");

    cdev_del(&chrdevTest.cdev);
    unregister_chrdev_region(chrdevTest.devid, chrdevTest_CNT);

    device_destroy(chrdevTest.class, chrdevTest.devid);
    class_destroy(chrdevTest.class);
}


module_init(containerTest_init);
module_exit(containerTest_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wp");

   最后交叉编译,下载到板子上,查看信息可得:

在这里插入图片描述

参考资料

[1] 一文搞懂 Linux 内核链表(深度分析)
[2] 【内核链表】数据结构——深入理解内核链表的概念和操作&笔记

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

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

相关文章

《A ConvNet for the 2020s》阅读笔记

论文标题 《A ConvNet for the 2020s》 面向 2020 年代的 ConvNet 作者 Zhuang Liu、Hanzi Mao、Chao-Yuan Wu、Christoph Feichtenhofer、Trevor Darrell 和 Saining Xie 来自 Facebook AI Research (FAIR) 和加州大学伯克利分校 初读 摘要 “ViT 盛 Conv 衰” 的现状&…

蓝桥杯2022年第十三届省赛真题-数的拆分

solution1&#xff08;通过10%&#xff09; #include<stdio.h> #include<math.h> typedef long long LL; int isPrime(LL n){LL sqr (int)sqrt(1.0 * n);for(int i 2; i < sqr; i){if(n % i 0) return 0;}return 1; } int main(){int t;LL a;scanf("%d…

IntelliJ IDEA 2023.3.4创建JavaWeb应用和集成Tomcat服务器

1. 创建项目 如下图所示&#xff0c;只需要给项目起一个项目名称&#xff0c;然后点击Create即可&#xff1a; 2. Project Structure 设置 创建完成后如下图 3. 集成Tomcat服务器 4. 实现Servlet接口 当我们实现Servlet接口时&#xff0c;发现没有Servlet相关的依赖时&am…

数学建模-估计出租车的总数

文章目录 1、随机抽取的号码在总体的排序 1、随机抽取的号码在总体的排序 10个号码从小到大重新排列 [ x 0 , x ] [x_0, x] [x0​,x] 区间内全部整数值 ~ 总体 x 1 , x 2 , … , x 10 总体的一个样本 x_1, x_2, … , x_{10} ~ 总体的一个样本 x1​,x2​,…,x10​ 总体的一个样…

深入浅出Hive性能优化策略

我们将从基础的HiveQL优化讲起&#xff0c;涵盖数据存储格式选择、数据模型设计、查询执行计划优化等多个方面。会的直接滑到最后看代码和语法。 目录 引言 Hive架构概览 示例1&#xff1a;创建表并加载数据 示例2&#xff1a;优化查询 Hive查询优化 1. 选择适当的文件格…

考研数二要掌握的高中知识点(四)

文章目录 一、正切函数的图像性质二、三角函数恒等变换公式1. 同角齐次式2. 两角和与差公式3. 辅助角公式4. 二倍角公式5. 降幂公式6. 半角公式&#xff08;二倍角公式的变形&#xff09;7. 万能公式 三、反三角函数1. 反正弦函数2. 反余弦函数3. 反正切函数 一、正切函数的图像…

保研|资讯|夏令营|3.31截止!香港城市大学市场营销学系首届学术暑期夏令营

香港城市大学市场营销学系首届学术暑期夏令营 1 项目简介 我们的博士项目致力为未来营销科学和工商管理学界培养一流学者和行业领袖。博士项目一般为期四到六年&#xff0c;允许本科生直接申请。课程包括实证分析模型&#xff0c;消费者行为研究&#xff0c;博弈微观模型&…

自定义方法SQL注入器-DefaultSqlInjector

/*** 自定义Sql注入* author zy*/ public class SqlInjector extends DefaultSqlInjector {Overridepublic List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {// 注意&#xff1a;此SQL注入器继承了DefaultSqlInjector(默认注入器…

【prometheus】k8s集群部署prometheus server(文末送书)

目录 一、概述 1.1 prometheus简介 1.2 prometheus架构图 1.3测试环境 二、k8s集群中部署prometheus server 2.1创建sa账号和数据目录 2.2安装prometheus 2.2.1创建configmap存储卷存放prometheus配置信息 2.2.2 通过deployment部署prometheus 2.2.3prometheus pod创…

HBase在表操作--显示中文

启动HBase后&#xff0c;Master和RegionServer两个服务器&#xff0c;分别对应进程为HMaster和HRegionServe。&#xff08;可通过jps查看&#xff09; 1.进入表操作 hbase shell 2.查看当前库中存在的表 list 3.查看表中数据&#xff08;注&#xff1a;学习期间可用&#…

Python小白笔记

输入 # 一行输入多个数字&#xff0c;空格隔开&#xff0c;存入列表a中 a list(map(int, input().split())) print(a) >>>21 22 34 54 67 >>>[21, 22, 34, 54, 67] 输出 数据&#xff1a; print(%d%10.3f%(x,y)) y的精度为3&#xff0c;宽度为10 %0 …

【Java】十大排序

目录 冒泡排序 选择排序 插入排序 希尔排序 归并排序 快速排序 堆排序 计数排序 桶排序 基数排序 冒泡排序 冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的序列&#xff0c;依次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。遍历…

oracle 19c打补丁到19.14

oracle 19c打补丁到19.14 oracle 19.3打补丁到19.14 查看oracle的版本&#xff1a; SQL> column product format A30 SQL> column version format A15 SQL> column version_full format A20 SQL> column status format A15 SQL> select * from product_compo…

【对顶队列】【中位数贪心】【前缀和】100227. 拾起 K 个 1 需要的最少行动次数

本文涉及知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 对顶队列&#xff08;栈&#xff09; 分类讨论 LeetCode100227. 拾起 K 个 1 需要的最少行动次数 给你一个下标从 0 开始的二进制数组 nums&#xff0c;其长度为 n &#x…

【LabVIEW FPGA入门】浮点数类型支持

如今&#xff0c;使用浮点运算来设计嵌入式系统的需求变得越来越普遍。随着 FPGA 因其固有的大规模并行性而在浮点性能方面继续超越微处理器&#xff0c;这种情况正在加剧。线性代数和数字信号处理 (DSP) 等高级算法可以受益于浮点数据类型的高动态范围精度。LabVIEW FPGA 通过…

Vue项目的搭建

Node.js 下载 Node.js — Download (nodejs.org)https://nodejs.org/en/download/ 安装 测试 winR->cmd执行 node -v配置 在安装目录下创建两个子文件夹node_cache和node_global,我的就是 D:\nodejs\node_cache D:\nodejs\node_global 在node_global文件下再创建一个…

视频基础知识(一) 视频编码 | H.26X 系列 | MPEG 系列 | H.265

文章目录 一、视频编码二、 H.26X 系列1、H.2612、H.2633、H.2643.1 I帧3.2 P帧3.3 B帧 4、H.265 三、 MPEG 系列1、MPEG-12、MPEG-23、MPEG-44、MPEG-7 &#x1f680; 个人简介&#xff1a;CSDN「博客新星」TOP 10 &#xff0c; C/C 领域新星创作者&#x1f49f; 作 者&…

【HTML】HTML表单8.2(表单标签2)

目录 接上期&#xff0c;大致实现效果 文章简要 注释&#xff1a;这一次介绍的很多效果需要后期与服务器配合&#xff0c;但我们这里先只介绍效果 ①提交按钮 ②获取验证码 ③上传文件 ④还原所有表单内容 ⑤下拉表单 ⑥文字域 接上期&#xff0c;大致实现效果 文章简要 注…

OpenCV-Java 开发简介

返回目录&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;如何在“Microsoft Visual Studio”中使用OpenCV编译应用程序 下一篇&#xff1a;如何将OpenCV Java 与Eclipse结合使用 警告&#xff1a; 本教程可能包含过时的信息。 …

伺服电机编码器的分辨率指得是什么?

伺服电机编码器的分辨率是伺服电机编码器的重要参数。 一般来说&#xff0c;具体的伺服电机编码器型号可以找到对应的分辨率值。 伺服电机编码器的分辨率和精度不同&#xff0c;但也有一定的关系。 伺服电机编码器的分辨率是多少&#xff1f; 1、伺服编码器&#xff08;同步伺…