ZYNQ之嵌入式驱动开发——字符设备驱动

文章目录

  • Linux驱动程序分类
  • Linux应用程序和驱动程序的关系
  • 简单的测试驱动程序
  • 在petalinux中添加LED驱动
  • 新字符设备驱动

Linux驱动程序分类

驱动程序分为字符设备驱动、块设备驱动和网络设备驱动。
字符设备是按字节访问的设备,比如以一个字节收发数据的串口,字符设备在Linux外设中占比最大。
块设备的特点是按一定格式存取的数据,具体的格式由文件系统决定。块设备以存储设备为主,存储设备的特点是以存储块为基础,因此得名块设备。
网络设备不同于上面两种,应用程序和网络设备驱动之间的通信由库和内核提供的一套数据包传输函数替代了open()、read()、write()等函数。


Linux应用程序和驱动程序的关系

(1)应用程序调用库函数提供的open()函数打开某个设备文件,该设备文件是在驱动加载成功之后在目录/dev中生成的,是应用程序调用相应硬件的入口。
(2)库根据open()函数的输入参数引起CPU异常进入内核,系统调用处于内核空间,应用程序无法直接访问,因此需要陷入到内核,方法就是软中断,陷入内核后还要指定系统调用号;
(3)内核的异常处理函数根据输入参数找到相应的驱动程序,返回文件句柄给库,库函数再返回给应用程序;
(4)应用程序再使用得到的文件句柄调用write()、read()等函数发出控制指令;
(5)库根据write()、read()等函数的输入参数引起CPU异常,进入内核;
(6)内核的异常处理函数根据输入参数调用相应的驱动程序执行相应的操作。
Linux应用程序调用驱动程序的步骤如下图所示。
在这里插入图片描述
应用程序中涉及到的open()、read()、write()等是由库提供的系统调用,通过执行某条指令引发异常进入内核,是应用程序操作硬件的途径。应用程序执行系统调用后进入内核,然后会使用驱动程序中对应的函数,驱动程序中的open()、read()、write()等函数是需要驱动开发人员实现的。应用程序运行于用户空间,驱动程序运行于内核空间,Linux系统可以通过MMU限制应用程序运行在某个内存块中,以避免这个应用程序出错导致整个系统崩溃,运行于内核空间的驱动程序是系统的一部分,驱动程序出错有可能牵连整个系统。


简单的测试驱动程序

在进行LED驱动开发之前,先使用下面的代码简单测试一下。

#include <linux/module.h>static int __init chardev_init(void)
{printk("Hello!\n");return 0;
}static void __exit chardev_exit(void)
{printk("GoodBye!\n");
}module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");

如果使用Linux内核编译上面的C文件,需要自己写Makefile,然后编译出驱动文件到7020开发板上验证,验证的结果如下图所示。
在这里插入图片描述
提示这个驱动是无效的模块格式,可能ZYNQ开发板就需要使用petalinux这样特定的工具进行开发,下面来看具体流程。
首先在petalinux的安装路径下设置环境变量。

source /opt/pkg/petalinux/settings.sh

进入到定制系统的根目录下,使用下面的命令添加字符设备驱动。

petalinux-create -t modules -n chardev

在当前路径下的/project-spec/meta-user/recipes-modules/下生成了一个名为chardev的文件夹,该文件夹下有以下三个文件,其中.c文件就是需要写入驱动代码的文件。
在这里插入图片描述
Makefile在创建工程的时候已经创建好了,里面的内容如下图所示,也不需要修改。
在这里插入图片描述
在C文件中写入驱动代码后,返回到自定义的/zynq7020目录下,使用下面的命令进行编译。

petalinux-build -c chardev

编译完成后的信息打印如下图所示。
在这里插入图片描述
由于编译成的驱动文件存放路径比较难找,因此直接在搜索栏中直接搜索驱动的名称就会出现,但是文件夹必须打开到自定义的工程的这一层才能搜索到。
在这里插入图片描述
可以右键该文件打开文件的具体存在位置为/opt/pkg/petalinux/zynq7020/build/tmp/sysroots-components/plnx_zynq7/chardev/lib /modules/4.14.0-xilinx-v2018.3/extra。
接下来就可以在开发板上验证该驱动了,验证的结果如下图所示。
在这里插入图片描述


在petalinux中添加LED驱动

同上面的示例,先在petalinux的安装路径下设置环境变量。

source /opt/pkg/petalinux/settings.sh

然后进入到定制系统的根目录下,使用下面的命令添加驱动。

petalinux-create -t modules -n psled1-driver

需要注意的是,驱动文件命名不能使用下划线,而要使用"-"代替。
在这里插入图片描述
创建成功以后,打印的消息提示创建的模块在当前路径下的/project-spec/meta-user/recipes-modules/psled1-driver中,进到这个目录下。在这里插入图片描述
一步步进到最终的目录下,在files文件夹下的.c文件就是要写入驱动代码的地方,在里面键入下面的代码。

//该代码来自ZYNQ教程,教程在文末给出
#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <linux/init.h>  
#include <linux/ide.h>  
#include <linux/types.h>  /* 驱动名称 */  
#define DEVICE_NAME       "ps_led1"  
/* 驱动主设备号 */  
#define GPIO_LED_MAJOR    200  /* gpio寄存器虚拟地址 */  
static unsigned int gpio_add_minor;  
/* gpio寄存器物理基地址 */  
#define GPIO_BASE         0xE000A000  
/* gpio寄存器所占空间大小 */  
#define GPIO_SIZE         0x1000  
/* gpio方向寄存器 */  
#define GPIO_DIRM_0       (unsigned int *)(0xE000A204 - GPIO_BASE + gpio_add_minor)  
/* gpio使能寄存器 */   
#define GPIO_OEN_0        (unsigned int *)(0xE000A208 - GPIO_BASE + gpio_add_minor)  
/* gpio控制寄存器 */  
#define GPIO_DATA_0       (unsigned int *)(0xE000A040 - GPIO_BASE + gpio_add_minor)  /* 时钟使能寄存器虚拟地址 */  
static unsigned int clk_add_minor;  
/* 时钟使能寄存器物理基地址 */  
#define CLK_BASE          0xF8000000  
/* 时钟使能寄存器所占空间大小 */  
#define CLK_SIZE          0x1000  
/* AMBA外设时钟使能寄存器 */  
#define APER_CLK_CTRL     (unsigned int *)(0xF800012C - CLK_BASE + clk_add_minor)        /* open函数实现, 对应到Linux系统调用函数的open函数 */  
static int gpio_leds_open(struct inode *inode_p, struct file *file_p)  
{  /* 把需要修改的物理地址映射到虚拟地址 */gpio_add_minor = (unsigned int)ioremap(GPIO_BASE, GPIO_SIZE);  clk_add_minor = (unsigned int)ioremap(CLK_BASE, CLK_SIZE);/* MIO_0时钟使能 */  *APER_CLK_CTRL |= 0x00400000;  /* MIO_0设置成输出 */  *GPIO_DIRM_0 |= 0x00000001;  /* MIO_0使能 */  *GPIO_OEN_0 |= 0x00000001;  printk("gpio_test module open\n");   return 0;  
}  /* write函数实现, 对应到Linux系统调用函数的write函数 */  
static ssize_t gpio_leds_write(struct file *file_p, const char __user *buf, size_t len, loff_t *loff_t_p)  
{  int rst;  char writeBuf[5] = {0};  printk("gpio_test module write\n");  rst = copy_from_user(writeBuf, buf, len);  if(0 != rst)  {  return -1;    }  if(1 != len)  {  printk("gpio_test len err\n");  return -2;  }  if(1 == writeBuf[0])  {  *GPIO_DATA_0 &= 0xFFFFFFFE;  printk("gpio_test ON\n");  }  else if(0 == writeBuf[0])  {  *GPIO_DATA_0 |= 0x00000001;  printk("gpio_test OFF\n");  }  else  {  printk("gpio_test para err\n");  return -3;  }    return 0;  
}  /* release函数实现, 对应到Linux系统调用函数的close函数 */  
static int gpio_leds_release(struct inode *inode_p, struct file *file_p)  
{     	printk("gpio_test module release\n");  return 0;  
}  /* file_operations结构体声明, 是上面open、write实现函数与系统调用函数对应的关键 */  
static struct file_operations gpio_leds_fops = {  .owner   = THIS_MODULE,  .open    = gpio_leds_open,  .write   = gpio_leds_write,     .release = gpio_leds_release,   
};  /* 模块加载时会调用的函数 */  
static int __init gpio_led_init(void)  
{  int ret;  /* 通过模块主设备号、名称、模块带有的功能函数(及file_operations结构体)来注册模块 */  ret = register_chrdev(GPIO_LED_MAJOR, DEVICE_NAME, &gpio_leds_fops);  if (ret < 0)   {  printk("gpio_led_dev_init_error\n");  return ret;  }  else  {  /* 注册成功 */ printk("gpio_led_dev_init_ok\n");  }  return 0;  
}  /* 卸载模块 */  
static void __exit gpio_led_exit(void)  
{  /* 释放对虚拟地址的占用 */  iounmap((unsigned int *)gpio_add_minor);  iounmap((unsigned int *)clk_add_minor); /* 注销模块, 释放模块对这个设备号和名称的占用 */  unregister_chrdev(GPIO_LED_MAJOR, DEVICE_NAME);printk("gpio_led_dev_exit_ok\n");  
}  /* 标记加载、卸载函数 */  
module_init(gpio_led_init);  
module_exit(gpio_led_exit);  /* 驱动描述信息 */  
MODULE_AUTHOR("Alinx");  
MODULE_ALIAS("gpio_led");  
MODULE_DESCRIPTION("GPIO LED driver");  
MODULE_VERSION("v1.0");  
MODULE_LICENSE("GPL");  

上面介绍的是直接编译驱动,也可以以图形化的形式进行编译,返回到自定义的/zynq7020目录下,输入下面的命令配置根文件系统。

petalinux-config -c rootfs

在弹出的图形化配置窗口中选择modules进入子菜单中。
在这里插入图片描述
按Y键将该驱动包括进来,然后保存退出。
在这里插入图片描述
根文件系统就配置成功了,然后使用petalinux-build命令编译该工程。
在这里插入图片描述
编译成功后打印下面的信息。
在这里插入图片描述
在/zynq7020目录下搜索驱动文件,如下图所示。
在这里插入图片描述
右键该文件选择打开文件存放位置,其存放在/zynq7020/build/tmp/sysroots-components/plnx_zynq7/psled1-driver/lib/modules/4.14.0-xilinx-v2018.3/extra,还是比较难找的,所以以后直接在工程目录下搜索即可。
在开发板上加载驱动,可以看到相应的设备号已经出现了。
在这里插入图片描述
使用下面的命令创建字符设备文件,指定主设备号和次设备号,设备文件名称为psled1,之后写应用程序的时候要使用该名称来操作字符设备。

mknod /dev/psled1 c 200 0

创建设备文件成功之后在/dev目录下就可以看到新添加的设备,如下图所示。
在这里插入图片描述
接下来写一个应用端的测试程序,用来传入数据点亮或者熄灭LED,程序如下。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd;int status;fd = open("/dev/psled1", O_RDWR);if(fd < 0){perror("open /dev/psled1 error!\n"); return fd; }status = atoi(argv[1]);write(fd, &status, 1);close(fd);  return 0;
}

将上面的程序通过交叉编译工具编译出适合在ARM平台运行的文件,将其发送到开发板验证,结果如下图所示。
在这里插入图片描述
如果采用下面的应用程序进行测试,LED将每隔一秒改变一下状态。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd;int status;fd = open("/dev/psled1", O_RDWR);if(fd < 0){perror("open /dev/psled1 error!\n"); return fd; }while(1){status = 1;write(fd, &status, 1);sleep(1);status = 0;write(fd, &status, 1);sleep(1);}close(fd);  return 0;
}

在开发板上执行后的结果如下图所示。
在这里插入图片描述
开发板上PS LED1的状态开始循环亮灭,如下动图所示。
请添加图片描述
终端里也是每隔一秒打印一次LED关闭或打开的状态。请添加图片描述
卸载驱动程序后打印下面的信息。
在这里插入图片描述

新字符设备驱动

上面驱动代码中将设备号写死了,这样做有很多不便之处,因为编译驱动代码前需要查看目标系统中设备号的占用情况,驱动注册函数中仅有主设备号没有次设备号,这意味着一个设备会占用所有的次设备号,十分浪费资源。针对这些问题,Linux内核提出了新的字符设备注册方法,并由内核来管理设备号。
注册字符设备号的函数原型如下。

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

from :需要申请的起始设备号,取代了原有的主设备号和次设备号,在需要指定主次设备号的情况下,可以通过方法from = MKDEV(major,minor); 来实现。
count :需要申请的设备号个数。
name :设备名称。
在不需要指定主次设备号的情况下,设备号由内核来分配,传入指针来获取设备号,注册、注销设备号的函数原型如下。

int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char* name);
void unregister_chrdev_region(dev_t from,unsigned count);

dev :设备号指针,注册成功之后,主次设备号可以通过 major = MAJOR(dev); minor = MINOR(dev); 来获取。
baseminor :次设备号的起始地址。
新的注册方法使用cdev结构体来定义一个字符设备,cdev结构体如下。

struct cdev
{struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};

cdev结构体的初始化函数原型如下。

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

注册、注销字符设备的函数原型如下。

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

类的创建和删除函数原型如下。

struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key);
void class_destroy(struct class *cls);

owner指定为THIS_MODULE,name是类的名称,第三个参数可以省略。
设备节点的创建和删除函数原型如下。

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

class是通过class_create创建的类,parent是父设备,无则填NULL,devt是设备号,drvdata是设备可能用到的数据,没有则填NULL,fmt是设备名,创建成功后在/dev下生成。
下面代码使用的是新字符设备方法。

#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>  
#include <linux/init.h>  
#include <linux/ide.h>  
#include <linux/types.h>  
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>#define DEVICE_NAME       "psled1"
#define DEVID_COUNT       1   //设备号个数 
#define DRIVE_COUNT       1   //驱动个数/* gpio寄存器虚拟地址 */  
static unsigned int gpio_add_minor;  
/* gpio寄存器物理基地址 */  
#define GPIO_BASE         0xE000A000  
/* gpio寄存器所占空间大小 */  
#define GPIO_SIZE         0x1000  
/* gpio方向寄存器 */  
#define GPIO_DIRM_0       (unsigned int *)(0xE000A204 - GPIO_BASE + gpio_add_minor)  
/* gpio使能寄存器 */   
#define GPIO_OEN_0        (unsigned int *)(0xE000A208 - GPIO_BASE + gpio_add_minor)  
/* gpio控制寄存器 */  
#define GPIO_DATA_0       (unsigned int *)(0xE000A040 - GPIO_BASE + gpio_add_minor)  /* 时钟使能寄存器虚拟地址 */  
static unsigned int clk_add_minor;  
/* 时钟使能寄存器物理基地址 */  
#define CLK_BASE          0xF8000000  
/* 时钟使能寄存器所占空间大小 */  
#define CLK_SIZE          0x1000  
/* AMBA外设时钟使能寄存器 */  
#define APER_CLK_CTRL     (unsigned int *)(0xF800012C - CLK_BASE + clk_add_minor)        #if 0
struct chardev
{dev_t            devid;      //设备号struct cdev      cdev;       //字符设备struct class     *class;     //类struct device    *device;    //设备节点
};static struct chardev alinx_char = {.cdev = {.owner = THIS_MODULE,},
};
#endifdev_t            devid;      //设备号
struct cdev      cdev;       //字符设备
struct class     *class;     //类
struct device    *device;    //设备节点static int gpio_leds_open(struct inode *inode_p, struct file *file_p)  
{  gpio_add_minor = (unsigned int)ioremap(GPIO_BASE, GPIO_SIZE);  clk_add_minor = (unsigned int)ioremap(CLK_BASE, CLK_SIZE); /* MIO_0时钟使能 */  *APER_CLK_CTRL |= 0x00400000;  /* MIO_0设置成输出 */  *GPIO_DIRM_0 |= 0x00000001;  /* MIO_0使能 */  *GPIO_OEN_0 |= 0x00000001;    printk("gpio_test module open\n");   return 0;  
}  static ssize_t gpio_leds_write(struct file *file_p, const char __user *buf, size_t len, loff_t *loff_t_p)  
{  int rst;  char writeBuf[5] = {0};   printk("gpio_test module write\n");  rst = copy_from_user(writeBuf, buf, len);  if(0 != rst)  {  return -1;    }  if(1 != len)  {  printk("gpio_test len err\n");  return -2;  }  if(1 == writeBuf[0])  {  *GPIO_DATA_0 &= 0xFFFFFFFE;  printk("gpio_test ON\n");  }  else if(0 == writeBuf[0])  {  *GPIO_DATA_0 |= 0x00000001;  printk("gpio_test OFF\n");  }  else  {  printk("gpio_test para err\n");  return -3;  }  return 0;  
}  static int gpio_leds_release(struct inode *inode_p, struct file *file_p)  
{  printk("gpio_test module release\n");  return 0;  
}  static struct file_operations chardev_fops = {  .owner   = THIS_MODULE,  .open    = gpio_leds_open,  .write   = gpio_leds_write,     .release = gpio_leds_release,   
};  static int __init gpio_led_init(void)  
{  #if 0alloc_chrdev_region(&alinx_char.devid, 0, DEVID_COUNT, DEVICE_NAME);  //注册设备号cdev_init(&alinx_char.cdev, &chardev_fops);  //初始化字符设备结构体cdev_add(&alinx_char.cdev, alinx_char.devid, DRIVE_COUNT);  //注册字符设备 alinx_char.class = class_create(THIS_MODULE, DEVICE_NAME);  //创建类if(IS_ERR(alinx_char.class)) {return PTR_ERR(alinx_char.class);}alinx_char.device = device_create(alinx_char.class, NULL, alinx_char.devid, NULL, DEVICE_NAME);  //创建设备节点printk("alloc success, major = %d minor = %d\n",MAJOR(alinx_char.devid),MINOR(alinx_char.devid));if (IS_ERR(alinx_char.device)) {return PTR_ERR(alinx_char.device);}#endifalloc_chrdev_region(&devid, 0, DEVID_COUNT, DEVICE_NAME);  //注册设备号cdev.owner = THIS_MODULE;cdev_init(&cdev, &chardev_fops);  //初始化字符设备结构体cdev_add(&cdev, devid, DRIVE_COUNT);  //注册字符设备 class = class_create(THIS_MODULE, DEVICE_NAME);  //创建类if(IS_ERR(class)) {return PTR_ERR(class);}device = device_create(class, NULL, devid, NULL, DEVICE_NAME);  //创建设备节点printk("alloc success, major = %d minor = %d\n",MAJOR(devid),MINOR(devid));if (IS_ERR(device)) {return PTR_ERR(device);}return 0;  
}static void __exit gpio_led_exit(void)  
{  iounmap((unsigned int *)gpio_add_minor);  iounmap((unsigned int *)clk_add_minor); #if 0cdev_del(&alinx_char.cdev);   //注销字符设备unregister_chrdev_region(alinx_char.devid, DEVID_COUNT);  //注销设备号device_destroy(alinx_char.class, alinx_char.devid);  //删除设备节点class_destroy(alinx_char.class);  //删除类#endifcdev_del(&cdev);   //注销字符设备unregister_chrdev_region(devid, DEVID_COUNT);  //注销设备号device_destroy(class, devid);  //删除设备节点class_destroy(class);  //删除类printk("gpio_led_dev_exit_ok\n");  
}  module_init(gpio_led_init);  
module_exit(gpio_led_exit);  
MODULE_LICENSE("GPL");  

加载驱动之后,内核就会为设备指定主设备号和次设备号,不用再使用命中自己指定了。
在这里插入图片描述


参考文档:course_s6_ZYNQ那些事儿-Linux驱动篇V1.05

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

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

相关文章

软信天成:业务流程管理驱动企业数字化转型

近日&#xff0c;在国家发展改革委办公厅、国家数据局综合司联合印发的《数字经济2024年工作要点》中&#xff0c;明确强调了本年度大力推进重点领域数字化转型&#xff0c;营造数字化转型生态的战略举措&#xff0c;标志着国家对于企业数字化转型的高度重视与积极倡导。 企业…

dubbo复习:(3) 服务超时时间配置

在dubbo admin中 可以进行类似如下配置 configVersion: v2.7 enabled: true configs:- side: consumeraddresses:- 0.0.0.0parameters:timeout: 55这样配置之后&#xff0c;当服务端响应超过55毫秒时&#xff0c;在服务消费者的控制台就会看到超时信息

(保姆级教程傻瓜式操作)树莓派--基于opencv实现人脸识别

前言 因为当时没有边实验边记录&#xff0c;所以这篇文章可能存在疏漏。不过很多地方我推荐了我参考过的博客或者视频&#xff0c;希望尽可能地解答您的疑惑&#xff0c;如果您仍有不懂的地方&#xff0c;欢迎评论&#xff0c;如果我知道答案&#xff0c;我会很乐意为您解答。 …

私活更好用:SpringBoot开源项目!!【送源码】

今天分享一款非常香的SpringBoot大屏开源项目&#xff0c;非常适合接私活用。 这是一款基于SpringBoot代码生成器的快速开发平台&#xff01;采用前后端分离架构&#xff1a;SpringBoot&#xff0c;Mybatis&#xff0c;Shiro&#xff0c;JWT&#xff0c;Vue&Ant Design。强…

MQTT_介绍_1.1

历史 1999年&#xff1a;MQTT最初由IBM的Andy Stanford-Clark和Cirrus Link的Arlen Nipper开发&#xff0c;用于满足石油和天然气公司在远程地区监控设备的需求。 2006年&#xff1a;IBM发布了MQTT的最初开源实现&#xff0c;但此时MQTT并未获得广泛的关注。 2010年&#xff…

三大平台直播视频下载保存方法

终于解决了视频号下载的问题&#xff0c;2024年5月15日亲测可用。 而且免费。 教程第二部分&#xff0c;有本地电脑无法下载的解决方案。 第一部分&#xff1a;使用教程&#xff08;正常&#xff09; 第1步&#xff1a;下载安装包 下载迅雷网盘搜索&#xff1a;大海福利合集…

【Python报错】Python安装模块时报错Fatal error in launcher

【Python报错】Python安装模块时报错Fatal error in launcher 最近需要用到python下载一个小工具&#xff0c;自信敲下回车键本想看到黑乎乎的终端上会出现快速跳跃的命令代码&#xff0c;没想到&#xff0c;报错了...... Fatal error in launcher: Unable to create process …

【Qt】Qt开源项目

1、Flameshot 截图工具 1.1 简介 Flameshot是一款功能强大但易于使用的屏幕截图软件,中文名称火焰截图。 Flameshot 简单易用并有一个CLI版本,所以可以从命令行来进行截图。 Flameshot 是一个Linux发行版中完全免费且开源的截图工具 1.2 源码 github:https://github.com…

智能监控与安全管理:安全帽检测算法的实践与应用

在工地、煤矿等高危工作环境中&#xff0c;安全帽的佩戴至关重要。安全帽能够有效防止因坠落物体或碰撞等引起的头部伤害&#xff0c;从而保护工作人员的生命安全。然而&#xff0c;传统的检查人员佩戴安全帽的方式主要依赖于现场监督和巡查&#xff0c;这种方法不仅耗费大量人…

不用投稿邮箱,怎样向各大新闻媒体投稿?

身为单位的信息宣传员,我深知肩上责任重大。每个月,完成单位在媒体上投稿发表文章的考核任务,就如同一场无声的赛跑,既要保证速度,更要注重质量。起初,我遵循“前辈们”的老路,一头扎进了邮箱投稿的海洋。但很快,现实给了我一记重拳——邮箱投稿的竞争犹如千军万马过独木桥,稿件…

Hadoop3:客户端向HDFS写数据流的流程讲解(较枯燥)

一、场景描述 我们登陆HDFS的web端&#xff0c;上传一个大文件。 二、流程图 三、讲解 流程1&#xff08;Client与NameNode交互&#xff09; 1、HDFS client创建DistributedFileSystem&#xff0c;通过dfs与NameNode进行2次&#xff08;一来一回4次&#xff09;对话&#x…

x264 帧类型代价计算原理:slicetype_mb_cost 函数分析

slicetype_mb_cost 函数 函数功能 计算每个宏块 MB 的代价 cost。函数参数分析 x264_t *h:全局编码结构体x264_mb_analysis_t *a:宏块分析结构体x264_frame_t **frames:系列帧数据结构体int p0:帧序号之一,一般指向靠前帧int p1:帧序号之一,一般指向靠后帧int b:帧标志…

Go微服务开源框架kratos的依赖注入关系总结

该文章为学习开源微服务框架kratos的学习笔记&#xff01;官方文档见&#xff1a;简介 | Kratos Kratos 一套轻量级 Go 微服务框架&#xff0c;包含大量微服务相关框架及工具。 一、Kratos 项目结构简介 通过 Kratos 工具生成的 Go工程化项目模板如下&#xff1a; applicati…

【半夜学习MySQL】复合查询(含多表查询、自连接、单行/多行子查询、多列子查询、合并查询等详解)

&#x1f3e0;关于专栏&#xff1a;半夜学习MySQL专栏用于记录MySQL数据相关内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 回顾基本查询多表查询自连接子查询单行子查询多行子查询多列子查询在from子句中使用子查询合并查询 回顾基本查询 下面使用…

计算机网络 -- 序列化与反序列化

一 协议的重要性 我们都知道&#xff0c;在进行网络通信的过程中&#xff0c;通信的双方可以是不同的设备&#xff0c;不同的平台&#xff0c;不同的平台&#xff0c;比如说&#xff0c;手机用户和电脑用户进行通信&#xff0c;ios系统和安卓系统进行通信。 自己的数据&#xf…

抖店商品详情API接口(店铺|标题|主图|价格|SKU属性等)

抖店商品详情API接口(店铺|标题|主图|价格|SKU属性等) 抖店商品详情API接口是指通过调用抖音开放平台提供的接口&#xff0c;获取抖店上商品的详细信息的方法。 抖店开放平台提供了一系列的接口&#xff0c;可以用于获取商品的基本信息、价格、库存、销量、评价等各种信息。以…

UIKit之图片浏览器

功能需求 实现一个图片浏览器&#xff0c;点击左右按钮可以切换背景图&#xff0c;且更新背景图对应的索引页和图片描述内容。 分析&#xff1a; 实现一个UIView的子类即可&#xff0c;该子类包含多个按钮。 实现步骤&#xff1a; 使用OC语言&#xff0c;故创建cocoa Touch类…

数据库的存储过程、函数与触发器

使用下面的场景来引入 1.创建表 CREATE DATABASE staff; USE staff; CREATE TABLE employee(id INT NOT NULL AUTO_INCREMENT,userName VARCHAR(255),birthDate DATE,idCard VARCHAR(255),loginName VARCHAR(255),PASSWORD VARCHAR(255),mobile VARCHAR(255),email VARCHAR(2…

开源连锁收银系统哪个好

针对开源连锁收银系统的选择&#xff0c;商淘云是一个备受关注的候选。商淘云以其功能丰富、易于定制和稳定性等优势&#xff0c;吸引了众多企业和开发者的关注。下面将从四个方面探讨商淘云开源连锁收银系统的优势&#xff1a; 首先&#xff0c;商淘云提供了丰富的功能模块。作…

如何查看SNMP设备的OID

什么是OID和MIB OID OID 代表对象标识符。 OID 唯一地标识 MIB 层次结构中的托管对象。 这可以被描述为一棵树&#xff0c;其级别由不同的组织分配。MIB MIB&#xff08;管理信息基&#xff09;提供数字化OID到可读文本的映射。 使用MIB Browser扫描OID 我的设备是一台UPS SN…