Linux第70步_新字符设备驱动的一般模板

1、了解“申请和释放设备号函数”

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

//注册字符设备驱动

//dev:保存申请到的设备号

//baseminor:次设备号的起始地址

//count:要申请的设备数量;

// name:表示“设备名字”

注意:

没有指定主设备号,但是给了“次设备号的基地址”和“次设备的数量”,可以使用alloc_chrdev_region()注册设备号;

int register_chrdev_region(dev_t from, unsigned count, const char *name)

from表示起始设备号

count表示次设备号的数量

name表示设备名

注意:

指定“起始设备号”和“次设备号的数量”,可以使用register_chrdev_region()注册设备号;

void unregister_chrdev_region(dev_t from, unsigned count)

//释放字符设备号

from表示起始设备号

count表示次设备的数量

注意:

指定“起始设备号”和“次设备的数量”,可以使用unregister_chrdev_region()注销设备号;

2、申请和释放设备应用举例:

int major; /* 主设备号 */

int minor; /* 次设备号 */

dev_t devid; /* 设备号 */

if (major)/* 定义了主设备号 */

{

devid = MKDEV(major, 0);

//major左移20位,再与0相或,就得到“Linux设备号”

//输入参数major为“主设备号”

//输入参数0为“次设备号”,大部分驱动次设备号都选择0

register_chrdev_region(devid, 1, "DevicName");

//注册设备号

//devid表示起始设备号

//1表示次设备号的数量

//DevicName表示设备名

}

else

{ /* 没有定义设备号 */

alloc_chrdev_region(&devid, 0, 1, "DevicName");

//注册字符设备驱动

//devid:保存申请到的设备号

//0:次设备号的起始地址

//1:要申请的设备数量;

// DevicName:表示“设备名字”

major = MAJOR(devid); /* 获取分配号的主设备号 */

//输入参数devid为“Linux设备号”

//devid右移20位得到“主设备号”

minor = MINOR(devid); /* 获取分配号的次设备号 */

//输入参数devid为“Linux设备号”

//devid与0xFFFFF相与后得到“次设备号”

}

unregister_chrdev_region(devid, 1);

/* 释放设备号 */

//devid:需要释放的设备号

//1:需要释放的次设备号数量;

4、了解“字符设备结构”,初始化字符设备,添加和删除字符设备的函数

“字符设备结构类型cdev”,位于在include/linux/cdev.h文件中,如下:

struct cdev {

  struct kobject kobj;

  struct module *owner;//使用THIS_MODULE将owner指针指向当前这个模块

  const struct file_operations *ops;//字符设备文件操作函数集合

  struct list_head list;

  dev_t dev;         //32位设备号

  unsigned int count;//次设备号数量

} __randomize_layout;

在include/linux/types.h文件中,可以查到如下:

typedef u32 __kernel_dev_t

//为“u32”起个别名叫“__kernel_dev_t”

typedef __kernel_dev_t dev_t;

//为“__kernel_dev_t”起个别名叫“dev_t”

void cdev_init(struct cdev *cdev, const struct file_operations *fops);

//初始化字符设备

//cdev是等待初始化的结构体变量

// fops就是字符设备文件操作函数集合

int cdev_add(struct cdev *p, dev_t dev, unsigned count);

//添加字符设备

// p表示指向要添加的字符设备,即字符设备结构cdev变量

// dev表示设备号

// count表示需要添加的设备数量

void cdev_del(struct cdev *p);

//删除字符设备

//p表示指向需要删除的字符设备,即字符设备结构cdev变量

5、初始化字符设备,添加和删除字符设备应用举例:

dev_t devid; /*声明32位变量devid用来给保存设备号 */

const struct file_operations  test_fops = {

  .owner = THIS_MODULE,

  .open = CharDeviceXXX_open,

  .read = CharDeviceXXX_read,

  .write = CharDeviceXXX_write,

  .release = CharDeviceXXX_release,

};

struct  cdev  test_cdev;//声明cdev字符设备结构变量test_cdev

Test_cdev.owner = THIS_MODULE;

//使用THIS_MODULE将owner指针指向当前这个模块

cdev_init(&test_cdev,& test_fops);

//初始化字符设备结构变量test_cdev”

//test_cdev是等待初始化的结构体变量

// test_fops就是字符设备文件操作函数集合

cdev_add(&testcdev, devid, 1);

//添加字符设备

// &testcdev表示指向要添加的字符设备,即字符设备结构testcdev变量

// devid表示设备号

// 1表示需要添加的设备数量

cdev_del(&testc_dev);

//删除字符设备

//&testc_dev表示指向需要删除的字符设备,即字符设备结构testc_dev变量

6、节点文件的自动创建与删除

设备文件的自动创建与删除是通过mdev用户程序来实现的。

 struct class *class_create (struct module *owner, const char *name);

//创建类

//owner一般为THIS_MODULE

//参数name是类名字

//返回值是指向结构体class的指针,也就是创建的类

void class_destroy(struct class *cls);

//删除类

//参数cls就是要删除的类

struct device *device_create(struct class *cls,struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)

//device_create是个可变参数的函数,用来创建设备

//参数cls就是设备要创建在哪个类下面

//参数parent是父设备,一般为 NULL,也就是没有父设备

//参数devt是设备号;

//参数drvdata 是设备可能会使用的一些数据,一般为 NULL;

//参数fmt是设备名字

//如果设置fmt=xxx 的话,就会生成/dev/xxx设备文件。

//返回值就是创建好的设备。

void device_destroy(struct class *cls, dev_t devt);

//删除创建的设备

//参数classs是要删除的设备所处的类

//参数devt是要删除的设备号

7、创建设备和删除设备举例:

struct class *class; /* 类 */

struct device *device; /* 设备 */

dev_t devid; /* 设备号 */

/* 驱动入口函数 */

static int __init xxx_init(void)

{

/* 创建类 */

class = class_create(THIS_MODULE, "Class_Name");

//创建类

//使用THIS_MODULE将owner指针指向当前这个模块

//Class_Name是类名字

//返回值是指向结构体class的指针,也就是创建的类

/* 创建设备 */

device = device_create(class, NULL, devid, NULL, "Class_Name");

//创建设备

//设备要创建在class类下面

//NULL表示没有父设备

//devid是设备号;

//参数drvdata=NULL,设备没有使用数据

//Class_Name是设备名字

//如果设置fmt=xxx 的话,就会生成/dev/xxx设备文件。

//返回值就是创建好的设备。

return 0;

}

/* 驱动出口函数 */

static void __exit led_exit(void)

{

/* 删除设备 */

device_destroy(newchrled.class, newchrled.devid);

//删除创建的设备

//newchrled.class是要删除的设备所处的类

//newchrled.devid是要删除的设备号

/* 删除类 */

class_destroy(newchrled.class);

//删除类

//newchrled.class就是要删除的类

}

module_init(led_init);

module_exit(led_exit);

6、创建NewCharDeviceXXX目录

输入“cd /home/zgq/linux/Linux_Drivers/回车

切换到“/home/zgq/linux/Linux_Drivers/”目录

输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/”目录的文件和文件夹

输入“mkdir NewCharDeviceXXX回车”,创建NewCharDeviceXXX目录

输入“cp CharDeviceXXX_1/* NewCharDeviceXXX/回车

将“CharDeviceXXX_1/”目录下的所有文件拷贝到“NewCharDeviceXXX/”目录下

输入“cd NewCharDeviceXXX/回车

切换到“/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX/”目录

输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX/”目录的文件和文件夹

输入“mv CharDeviceXXX.c NewCharDeviceXXX.c回车

将“CharDeviceXXX.c”更名为“NewCharDeviceXXX.c”

输入“mv CharDeviceXXX_APP.c NewCharDeviceXXX_APP.c回车

将“CharDeviceXXX_APP.c”更名为“NewCharDeviceXXX_APP.c

输入“ls回车”查看“/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX/”目录的文件和文件夹

7、修改Makefile文件

打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“Linux_Drivers”,点击“NewCharDeviceXXX”。

修改后Makefile文件如下:

KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31

#使用“:=”将其后面的字符串赋值给KERNELDIR

CURRENT_PATH := $(shell pwd)

#采用“shell pwd”获取当前打开的路径

#使用“$(变量名)”引用“变量的值”

obj-m := NewCharDeviceXXX.o

#给“obj-m”赋值为“NewCharDeviceXXX.o”

drv: kernel_modules

#生成“drv”需要依赖“kernel_modules”

    @echo $(KERNELDIR)

#输出KERNELDIR的值为“/home/zgq/linux/atk-mp1/linux/linux-5.4.31”

    @echo $(CURRENT_PATH)

#输出CURRENT_PATH的值为/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX”

    @echo $(MAKE)

#输出MAKE的值为make

kernel_modules:

    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

#后面的"modules"表示编译成模块

#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”

#“CURRENT_PATH”上面定义为“当前的工作目录”

#“-C $(KERNELDIR) M=$(CURRENT_PATH) ”表示将“当前的工作目录”切换到“指定的目录”中

#即切换到“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”。

#M表示模块源码目录

#在“make和modules”之间加入“M=$(CURRENT_PATH)”,表示切换到由“CURRENT_PATH”指定的目录中读取源码,同时将其编>译为.ko 文件

clean_drv:

    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”

#“CURRENT_PATH”上面定义为“当前的工作目录

app:

    arm-none-linux-gnueabihf-gcc NewCharDeviceXXX_APP.c -o NewCharDeviceXXX_APP

clean_app:

    rm NewCharDeviceXXX_APP

8、添加“c_cpp_properties.json

按下“Ctrl+Shift+P”,打开VSCode控制台,然后输入“C/C++:Edit Configurations(JSON)”,打开以后会自动在“.vscode ”目录下生成一个名为“c_cpp_properties.json” 的文件。

修改c_cpp_properties.json内容如下所示:

{

    "configurations": [

        {

            "name": "Linux",

            "includePath": [

                "${workspaceFolder}/**",

               "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31",

                "/home/zgq/linux/Linux_Drivers/NewCharDeviceXXX",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include",

"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"

],

            "defines": [],

            "compilerPath": "/usr/bin/gcc",

            "cStandard": "gnu11",

            "cppStandard": "gnu++14",

            "intelliSenseMode": "gcc-x64"

        }

    ],

    "version": 4

}

9、NewCharDeviceXXX.c文件如下:

#include <linux/types.h>

//数据类型重命名

//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t

//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t

#include <linux/kernel.h>

#include <linux/delay.h>

#include <linux/ide.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/string.h>

#include <linux/cdev.h> /*字符设备结构cdev定义在linux/cdev.h文件里*/

#include <linux/mdev.h>//自动创建和删除“设备节点文件”

#include <linux/device.h>/* 类class定义在linux/device.h文件里*/

#define NewCharDeviceXXX_CNT    1   //定义设备数量为1

#define NewCharDeviceXXX_NAME  "NewCharDeviceXXXName"//定义设备的名字

/* 设备结构体 */

struct CharDeviceXXX_dev{

  dev_t devid; /*声明32位变量devid用来给保存设备号 */

  int major; /* 主设备号 */

  int minor; /* 次设备号 */

  struct cdev  cdev; /*字符设备结构cdev定义在linux/cdev.h文件里*/

  struct class *class; /* 类 ,class定义在linux/device.h文件里*/

  struct device *device;/*设备*/

};

struct CharDeviceXXX_dev strCharDeviceXXX;

static char CharDeviceXXX_readbuf[100]; //读缓冲区

static char CharDeviceXXX_writebuf[100]; //写缓冲区

static char My_DataBuffer[] = {"My Data!"};

/* 打开设备 */

static int CharDeviceXXX_open(struct inode *inode, struct file *filp)

{

  /* 用户实现具体功能 */

  printk("CharDeviceXXX_open!\r\n");

  return 0;

}

/* 从设备读取数据,保存到首地址为buf的数据块中,长度为cnt个字节 */

//file结构指针变量flip表示要打开的设备文件

//buf表示用户数据块的首地址

//cnt表示用户数据的长度,单位为字节

//loff_t结构指针变量offt表示“相对于文件首地址的偏移”

static ssize_t CharDeviceXXX_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)

{

  int ret = 0;

  memcpy(CharDeviceXXX_readbuf, My_DataBuffer,sizeof(My_DataBuffer));

  //将My_DataBuffer[]中的所有数据拷贝到CharDeviceXXX_readbuf[]

  ret = copy_to_user( buf, CharDeviceXXX_readbuf, cnt );

  //将CharDeviceXXX_readbuf[]中的前cnt个字节拷贝到buf[]中

  if(ret==0) printk("Driver send the data to the user, and the result is ok!\r\n");

  else printk("Driver send the data to the user, and the result is failed!\r\n");

  return 0;

}

/* 向设备写数据,将数据块首地址为buf的数据,长度为cnt个字节,发送给用户 */

//file结构指针变量flip表示要打开的设备文件

//buf表示用户数据块的首地址

//cnt表示用户数据的长度,单位为字节

//loff_t结构指针变量offt表示“相对于文件首地址的偏移”

static ssize_t CharDeviceXXX_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)

{

  int ret = 0;

  ret = copy_from_user(CharDeviceXXX_writebuf, buf, cnt);

  //将buf[]中的前cnt个字节拷贝到CharDeviceXXX_writebuf[]中

  if(ret==0) printk("Driver receive the data form user , and the result is ok!\r\n");

  else printk("Driver receive the data form user , and the result is failed!\r\n");

  return 0;

}

/* 关闭/释放设备 */

static int CharDeviceXXX_release(struct inode *inode, struct file *filp)

{

  /* 用户实现具体功能 */

  printk("CharDeviceXXX_release!\r\n");

  return 0;

}

/*声明file_operations结构变量MyCharDevice_fops*/

/*它是指向设备的操作函数集合变量*/

const struct file_operations CharDeviceXXX_fops = {

  .owner = THIS_MODULE,

  .open = CharDeviceXXX_open,

  .read = CharDeviceXXX_read,

  .write = CharDeviceXXX_write,

  .release = CharDeviceXXX_release,

};

/*驱动入口函数 */

static int  __init CharDeviceXXX_init(void)

{

  int ret;

  /* 1、寄存器地址映射 */

  

  /*2、创建设备号*/

  strCharDeviceXXX.major=0;

  if(strCharDeviceXXX.major)/*如果指定了主设备号*/

  {

    strCharDeviceXXX.devid = MKDEV(strCharDeviceXXX.major, 0);

    //输入参数strCharDeviceXXX.major为“主设备号”

    //输入参数0为“次设备号”,大部分驱动次设备号都选择0

//将strCharDeviceXXX.major左移20位,再与0相或,就得到“Linux设备号”

   

ret=register_chrdev_region(strCharDeviceXXX.devid, NewCharDeviceXXX_CNT, NewCharDeviceXXX_NAME);

//申请设备号

    //strCharDeviceXXX.devid表示起始设备号

    //NewCharDeviceXXX_CNT表示次设备号的数量

    //NewCharDeviceXXX_NAME表示设备名

    if(ret < 0) //申请设备号失败

      goto fail_map;

  }

  else

  { /* 没有定义设备号 */

    ret=alloc_chrdev_region(&strCharDeviceXXX.devid, 0, NewCharDeviceXXX_CNT,NewCharDeviceXXX_NAME);

/* 申请设备号 */

    //strCharDeviceXXX.devid:保存申请到的设备号

    //0:次设备号的起始地址

    //NewCharDeviceXXX_CNT:要申请的次设备号数量;

    //NewCharDeviceXXX_NAME:表示“设备名字”

    if(ret < 0) //申请设备号失败

      goto fail_map;//去释放“物理地址内存映射”

strCharDeviceXXX.major = MAJOR(strCharDeviceXXX.devid);

/* 获取分配号的主设备号 */

//输入参数strCharDeviceXXX.devid为“Linux设备号”

//将strCharDeviceXXX.devid右移20位得到“主设备号”

strCharDeviceXXX.minor = MINOR(strCharDeviceXXX.devid);

/* 获取分配号的次设备号 */

   //输入参数strCharDeviceXXX.devid为“Linux设备号”

   //将strCharDeviceXXX.devid与0xFFFFF相与后得到“次设备号”

  }

  /*3、注册字符设备*/

  strCharDeviceXXX.cdev.owner = THIS_MODULE;

//使用THIS_MODULE将owner指针指向当前这个模块

  cdev_init(&strCharDeviceXXX.cdev,&CharDeviceXXX_fops);

  //注册字符设备,初始化“字符设备结构变量strCharDeviceXXX.cdev”

  //strCharDeviceXXX.cdev是等待初始化的结构体变量

  //CharDeviceXXX_fops就是字符设备文件操作函数集合

/*4、添加字符设备cdev*/      ret=cdev_add(&strCharDeviceXXX.cdev,strCharDeviceXXX.devid,NewCharDeviceXXX_CNT);

//添加字符设备

/*&strCharDeviceXXX.cdev表示指向要添加的字符设备,即字符设备结构strCharDeviceXXX.cdev变量*/

//strCharDeviceXXX.devid表示设备号

//NewCharDeviceXXX_CNT表示需要添加的设备数量

  if(ret < 0 ) //添加字符设备失败

    goto del_register;//去执行删除“已经注册的字符设备”

  printk("dev id major = %d,minor = %d\r\n", strCharDeviceXXX.major, strCharDeviceXXX.minor);

  printk("CharDeviceXXX_init is ok!!!\r\n");

  /*5、创建类*/

  strCharDeviceXXX.class = class_create(THIS_MODULE, NewCharDeviceXXX_NAME);

//创建类

//使用THIS_MODULE将owner指针指向当前这个模块

//NewCharDeviceXXX_NAME是类名字

//返回值是指向结构体class的指针,也就是创建的类

  if(IS_ERR(strCharDeviceXXX.class)){

    goto del_cdev; //去执行删除“已添加的字符设备”

  }

  /*6、创建设备 */

  strCharDeviceXXX.device = device_create(strCharDeviceXXX.class, NULL, strCharDeviceXXX.devid, NULL, NewCharDeviceXXX_NAME);

//创建设备

//设备要创建在strCharDeviceXXX.class类下面

//NULL表示没有父设备

//strCharDeviceXXX.devid是设备号;

//参数drvdata=NULL,设备没有使用数据

//NewCharDeviceXXX_NAME是设备名字

/*如果设置fmt=NewCharDeviceXXX_NAME 的话,就会生成/dev/NewCharDeviceXXX_NAME设备文件*/

  //返回值就是创建好的设备。

   if(IS_ERR(strCharDeviceXXX.device)){

     goto destroy_class;

  }

  return 0;//驱动初始化正确

destroy_class:

  class_destroy(strCharDeviceXXX.class);

  //删除类

  //strCharDeviceXXX.class就是要删除的类

del_cdev:

   cdev_del(&strCharDeviceXXX.cdev);

   //删除字符设备

   //&strCharDeviceXXX.cdev表示指向需要删除的字符设备,即字符设备结构strCharDeviceXXX.cdev变量

del_register:

  unregister_chrdev_region(strCharDeviceXXX.devid, NewCharDeviceXXX_CNT);

/* 释放设备号 */

//strCharDeviceXXX.devid:需要释放的起始设备号

//NewCharDeviceXXX_CNT:需要释放的次设备号数量;

fail_map://申请设备号失败

  /*若有物理地址映射到内存,则释放内存*/

  return -EIO; //驱动初始化失败

}

/*驱动出口函数 */

static void __exit CharDeviceXXX_exit(void)

{

/*1、释放内存*/

  /*2、 释放设备号 */

unregister_chrdev_region(strCharDeviceXXX.devid,NewCharDeviceXXX_CNT);

/* 释放设备号 */

//strCharDeviceXXX.devid:需要释放的起始设备号

//NewCharDeviceXXX_CNT:需要释放的次设备号数量;

/*3、删除字符设备*/

  cdev_del(&strCharDeviceXXX.cdev);

 /*删除字符设备*/

 /*&strCharDeviceXXX.cdev表示指向需要删除的字符设备,即字符设备结构strCharDeviceXXX.cdev变量*/

/*4、 删除设备 */

device_destroy(strCharDeviceXXX.class, strCharDeviceXXX.devid);

//删除创建的设备

//newchrled.class是要删除的设备所处的类

//newchrled.devid是要删除的设备号

/*5、删除类*/

class_destroy(strCharDeviceXXX.class);

//删除类

//strCharDeviceXXX.class就是要删除的类

}

module_init(CharDeviceXXX_init);

//指定CharDeviceXXX_init()为驱动入口函数

module_exit(CharDeviceXXX_exit);

//指定CharDeviceXXX_exit()为驱动出口函数

MODULE_AUTHOR("Zhanggong");//添加作者名字

MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”

MODULE_INFO(intree,"Y");

//去除显示“loading out-of-tree module taints kernel.”

10、编译

由于NewCharDeviceXXX_APP.c和CharDeviceXXX_APP.c内容相同,只是修改了文件名,不再重写,参考前面的文章“Linux第68步_旧字符设备驱动的一般模板”

输入“make clean_drv回车”,清除NewCharDeviceXXX.*

输入“make drv回车”,编译生成NewCharDeviceXXX.ko

输入“make clean_app回车”,清除NewCharDeviceXXX_APP

输入“make app回车”,编译生成NewCharDeviceXXX_APP

输入“ls -l回车

输入“sudo cp NewCharDeviceXXX.ko NewCharDeviceXXX_APP /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f回车

11、测试

启动开发板,从网络下载程序

输入“root

输入“cd /lib/modules/5.4.31/回车

切换到“/lib/modules/5.4.31/”目录

注意:“lib/modules/5.4.31/在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中

输入“ls”查看“NewCharDeviceXXX.ko和NewCharDeviceXXXApp”是否存在

输入“depmod”,驱动在第一次执行时,需要运行“depmod”

输入“modprobe NewCharDeviceXXX.ko”,加载“NewCharDeviceXXX.ko”模块

输入“lsmod”查看有哪些驱动在工作

输入“ls /dev/NewCharDeviceXXXName -l回车”,发现节点文件“/dev/NewCharDeviceXXXName

输入“./NewCharDeviceXXX_APP /dev/NewCharDeviceXXXName 1回车”执行读操作

输入“./NewCharDeviceXXX_APP /dev/NewCharDeviceXXXName 2回车”执行写操作

输入“rmmod NewCharDeviceXXX.ko”,卸载“NewCharDeviceXXX.ko”模块

注意:输入“rmmod NewCharDeviceXXX”也可以卸载“NewCharDeviceXXX.ko”模块

输入“lsmod”查看有哪些驱动在工作。

输入“ls /dev/NewCharDeviceXXXName -l回车”,查询节点文件“/dev/NewCharDeviceXXXName”是否存在

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

低密度奇偶校验码LDPC(九)——QC-LDPC译码器FPGA全并行设计

往期博文 低密度奇偶校验码LDPC&#xff08;一&#xff09;——概述_什么是gallager构造-CSDN博客 低密度奇偶校验码LDPC&#xff08;二&#xff09;——LDPC编码方法-CSDN博客 低密度奇偶校验码LDPC&#xff08;三&#xff09;——QC-LDPC码概述-CSDN博客 低密度奇偶校验码…

【操作系统概念】 第3章:进程

文章目录 0.前言3.1进程概念3.1.1 进程3.1.2 进程状态3.1.3 进程控制块&#xff08;PCB&#xff09; 3.2、进程调度3.2.1 调度队列3.2.2 调度程序3.2.3 上下文切换 3.3 进程操作3.3.1 进程创建3.3.2 进程终止 3.4 进程间通信 0.前言 早期的计算机一次只能执行一个程序。这种程序…

安泰ATA-P1005压电叠堆放大器在纳米定位台驱动中的应用

纳米定位台是一种高精度的微纳米级定位设备&#xff0c;主要用于微纳米加工、显微镜下的样品定位、纳米精度的测量和调试等领域。内置高性能压电陶瓷&#xff0c;运动范围可达500μm&#xff0c;具有体积小、无摩擦、响应速度快、高精度位移等特点&#xff0c;ATA-P1005压电叠堆…

stm32学习笔记:I2C通信外设原理(未完)

软件实现和硬件实现 串口通信为异步时序&#xff0c;用软件实现很麻烦&#xff0c;基本上用硬件实现 而I2C协议通信为同步时序&#xff0c;软件实现简单且灵活&#xff0c;硬件实现比较麻烦&#xff0c;故软件比较常用 但I2C硬件实现功能比较大&#xff0c;执行效率高&#xff…

【Proteus仿真】【Arduino单片机】坐姿矫正提醒器设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用LCD1602液晶显示模块、HC-SR04超声波模块、蜂鸣器、按键、人体红外传感器等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示超声…

Linux运维:在线/离线安装Telnet客户端和Telnet服务

Linux运维&#xff1a;在线/离线安装Telnet客户端和Telnet服务 前言1.1 在线安装Telnet1.2 离线安装Telnet1.3 Telnet服务有关的命令 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 前言 Telnet是一种用于远程登录到其他计算机的协议&…

未来已来!AI大模型引领科技革命

未来已来&#xff01;AI大模型正以惊人的速度引领着科技革命。随着科技的发展&#xff0c;人工智能在各个领域展现出了非凡的能力和潜力&#xff0c;大模型更是成为了科技领域的明星。从自然语言处理到图像识别&#xff0c;从智能推荐到语音识别&#xff0c;大模型的应用正在改…

Nuxt2升级Nuxt3指南(一):升级准备

一、项目环境说明 升级前&#xff1a;Node: 14.19.1; Nuxt&#xff1a;2.12.2&#xff1b;Vue&#xff1a;2.6.11&#xff1b;Element-UI&#xff1a;2.4.11 升级后&#xff1a; Node: 18.14.0 ; Nuxt: 3.10.3&#xff1b;Vue&#xff1a;3.4.19&#xff1b;Element-Plus&#…

分布式数字身份:通往Web3.0世界的个人钥匙

数字化时代&#xff0c;个人身份已不再仅仅局限于传统形式&#xff0c;分布式数字身份&#xff08;Decentralized Identity&#xff0c;简称DID&#xff09;正崭露头角&#xff0c;它允许个人通过数字签名等加密技术&#xff0c;完全掌握和控制自己的身份信息。研究报告显示&am…

Polar 写shell

Polar 写shell 直接给了源码 还是没啥好说的&#xff0c;考点是die()死亡函数绕过之不同变量 **绕过原理&#xff1a; **通过base64解密或rot13解密使"<?php exit();"变为乱码&#xff0c;而传入的$content为base64编码&#xff0c;解码后为正常shell语句。通过…

智慧城市中的数字孪生:构建城市管理的未来框架

目录 一、引言 二、数字孪生技术概述 三、数字孪生技术在智慧城市中的应用 1、实时监测与预警 2、模拟与优化 3、智能化决策 4、协同与共享 四、数字孪生技术构建城市管理的未来框架的价值 1、提高管理效率 2、优化资源配置 3、提升公共服务水平 4、增强应对突发事…

K8s集群调度,亲和性,污点,容忍,排障

目录 1.调度约束 调度过程 指定调度节点 查看详细事件 获取标签帮助 修改成 nodeSelector 调度方式 2.亲和性 节点亲和性 Pod 亲和性 键值运算关系 硬策略 软策略 Pod亲和性与反亲和性 创建一个标签为 appmyapp01 的 Pod 使用 Pod 亲和性调度&#xff0c;创建多…