Linux第68步_旧字符设备驱动的一般模板

file_operations结构体中的函数就是我们要实现的具体操作函数。

注意:

register_chrdev()和 unregister_chrdev()这两个函数是老版本驱动使用的。现在新字符设备驱动已经不再使用这两个函数,而是使用Linux内核推荐的新字符设备驱动API函数

1、创建CharDeviceXXX.c

输入“cd /home/zgq/linux/Linux_Drivers/回车”,切换到“/home/zgq/linux/Linux_Drivers/”目录

输入“ls回车”,查看“/home/zgq/linux/Linux_Drivers/”目录

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

输入“ls回车”,查看“/home/zgq/linux/Linux_Drivers/”目录

输入“cd CharDeviceXXX回车”,切换到“/home/zgq/linux/Linux_Drivers/ CharDeviceXXX/”目录

输入“vi CharDeviceXXX.c回车”,打开“CharDeviceXXX.c

CharDeviceXXX.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/device.h>     //必须要包含的头文件

//#include <linux/fs.h>         //使能结构体"file_operations"

#define CharDeviceXXX_MAJOR   200

//定义主设备号

//静态分配设备号:在串口输入“cat/proc/devices”查询当前已用的主设备号

//然后使用一个“没有被使用的设备号”作为该设备的的主设备号

#define CharDeviceXXX_NAME   "CharDeviceXXXName"  //定义设备的名字

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("Send the data to the user, and the result is ok!\r\n");

  else printk("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("Receive the data form user , and the result is ok!\r\n");

  else printk("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;

  ret = register_chrdev(CharDeviceXXX_MAJOR, CharDeviceXXX_NAME, &CharDeviceXXX_fops);

  //注册字符设备驱动

  //CharDeviceXXX_MAJOR为主设备号,采用宏CharDeviceXXX_NAME定义设备名字

  //CharDeviceXXX_fops是设备的操作函数集合,它是file_operations结构变量

  if (ret < 0)

  {

    pr_err("CharDeviceXXX_init is failed!!!\r\n");

    return ret;

  }

  else pr_err("CharDeviceXXX_init is ok!!!\r\n");

  return 0;

}

/*驱动出口函数 */

static void __exit CharDeviceXXX_exit(void)

{/*出口函数具体内容 */

  unregister_chrdev(CharDeviceXXX_MAJOR, CharDeviceXXX_NAME);

  //注销字符设备驱动

  //CharDeviceXXX_MAJOR为主设备号,采用宏CharDeviceXXX_NAME定义设备名字

}

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.”

2、Makefile文件的一般模板

输入“vi Makefile回车

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

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

CURRENT_PATH := $(shell pwd)

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

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

obj-m := CharDeviceXXX.o

#生成“obj-m”需要依赖“CharDeviceXXX.o

build: kernel_modules

#生成“build”需要依赖“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/CharDeviceXXX

@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:

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

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

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

3、创建“c_cpp_properties.json

打开VSCode,按下“Ctrl+Shift+P”,打开VSCode控制台,然后输入“C/C++:Edit Configurations(JSON)”,见下图:

打开以后会自动在“.vscode ”目录下生成一个名为“c_cpp_properties.json” 的文件,此文件默认内容如下所示:

{

    "configurations": [

        {

            "name": "Linux",

            "includePath": [

                "${workspaceFolder}/**"

            ],

            "defines": [],

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

            "cStandard": "gnu11",

            "cppStandard": "gnu++14",

            "intelliSenseMode": "gcc-x64"

        }

    ],

    "version": 4

}

修改“includePath”后,“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/CharDeviceXXX_1",

"/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

}

4、了解APP程序需要用到的相关函数

编写Linux应用程序,需要用到C库里面“和文件操作有关”的函数。

1)、open()函数

int open(const char *pathname, int flags)

指针变量pathname表示要打开的设备文件名;

flags表示“文件打开模式”:O_RDONLY表示只读模式;

O_WRONLY表示只写模式;

O_RDWR表示读写模式;

其他可选模式:O_APPEND表示每次写操作都写入文件的尾部;O_CREAT表示如果指定文件不存在,则创建这个文件;

O_EXCEL表示如果要创建的文件已经存在,则返回-1,并修改errno的值;O_TRUNC表示如果文件存在,并且以“只写或读写”方式打开,则清空文件全部内容;O_NOCTTY表示如果路径名指向终端设备,不要把这个设备用作控制终端;O_NONBLOCK表示如果路径名指向FIFO/块文字/字符文件,则把文件的打开和后继I/O设置为非阻塞;O_DSYNC表示等待物理I/O结束后再write。在不影响读取新写入的数据的前提下,不等待文件属性更新;O_RSYNC表示read等待所有写入同一区域的写操作完成后再进行;O_SYNC表示等待物理I/O结束后再write,包括更新文件属性的IO;

返回值:如果文件打开成功,则返回“文件描述符”。

在终端输入“man 2 open”可以查询open()这个函数

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

需要包含上面的头文件,才可以使用open()函数

2)、read()函数

ssize_t read(int fd, void *buf, size_t count)

fd表示要进行读操作的“文件描述符”

buf表示将读到的数据保存到“以buf为首地址的数据块”

count表示读取到的“数据长度”,单位为字节

返回值:

大于0表示读取到的字节数

0表示读到了文件的尾部

小于0表示读取失败

在终端输入“man 2 read”可以查询read()这个函数

#include <unistd.h>

需要包含上面这个头文件,才可以使用read()函数

3)、write()函数

ssize_t write(int fd, const void *buf, size_t count)

fd表示要进行写操作的“文件描述符”

buf和count表示将“以buf为首地址的数据块”,长度为count个字节,发送给用户

返回值:

大于0表示写入的字节数

0表示没有写入任何数据

小于0表示写入失败

在终端输入“man 2 write”可以查询write()这个函数

#include <unistd.h>

需要包含上面这个头文件,才可以使用write()函数

4)、close()函数

int close(int fd)

fd表示要关闭的“文件描述符”

返回值:

0表示关闭成功

小于0表示关闭失败

在终端输入“man 2 close”可以查询close()这个函数

#include <unistd.h>

需要包含上面这个头文件,才可以使用close()函数

在终端输入“man 3 memcpy”在第3节中可以查询memcpy()这个函数

#include <string.h>

需要包含上面这个头文件,才可以使用memcpy()函数

linux之man命令 (baidu.com)

man后面的数字代表的内容

1:用户在shell环境可操作的命令或执行文件;如输入“man 1 ls”就可以查询ls命令

2:系统内核可调用的函数与工具等;如输入“man 2 read”就可以查询read()函数

3:一些常用的函数(function)与函数库(library),大部分为C的函数库(libc);

;如输入“man 3 strstr”就可以查询strstr()函数

4:设备文件说明,通常在/dev下的文件

5:配置文件或某些文件格式

6:游戏(games)

7:惯例与协议等,如Linux文件系统,网络协议,ASCII code等说明

8:系统管理员可用的管理命令

9:跟kernel有关的文件。

5、编写CharDeviceXXX_APP.c

#include "stdio.h"

#include "unistd.h"

#include "sys/types.h"

#include "sys/stat.h"

#include "fcntl.h"

#include "stdlib.h"

#include "string.h"

static char usrdataBuffer[] = {"usr data!"};

//例如当argv[]是指向输入参数“./CharDeviceXXX /dev/chrdevbase 1”

/*

参数argc: argv[]数组元素个数

参数argv[]:是一个指针数组

返回值: 0 成功;其他 失败

*/

int main(int argc, char *argv[])

{

  int fd, retvalue;

  char *filename;

  char readbuf[100], writebuf[100];

  if(argc != 3)

  {

    printf("Error Usage!\r\n");

    return -1;

  }

  //argv[]是指向输入参数“./CharDeviceXXXApp” “/dev/CharDeviceXXX” “1”

  filename = argv[1];

  //argv[1]指向字符串“/dev/CharDeviceXXX”

  fd = open(filename, O_RDWR);

  //以“读写方式”打开“/dev/CharDeviceXXX”文件,若成功则fd为“文件描述符”

  //fd=0表示标准输入流; fd=1表示标准输出流;fd=2表示错误输出流;

  if(fd < 0)

  {

    printf("Can't open file %s\r\n", filename);

    return -1;

  }

  else printf("open file: %s\r\n", filename);

  if(atoi(argv[2]) == 1)

  {

    retvalue = read(fd, readbuf, 50);

    //fd表示要进行读操作的“文件描述符”

    //readbuf表示将读到的数据保存到“以readbuf为首地址的数据块”

    //50表示读取到的“数据长度”,单位为字节

    //返回值大于0表示读取到的字节数

    //返回值为0表示读到了文件的尾部

    //返回值小于0表示读取文件失败

    if(retvalue < 0)//读取文件失败

    {

      printf("read file %s failed!\r\n", filename);

    }

    else//读取文件成功

    {

      printf("read data:%s\r\n",readbuf);

    }

  }

  if(atoi(argv[2]) == 2)

  {

memcpy(writebuf, usrdataBuffer, sizeof(usrdataBuffer));

//将usrdataBuffer[]中所有数据拷贝到writebuf[]

    retvalue = write(fd, writebuf, 50);

    //fd=2表示要进行写操作的“文件描述符”

    //将writebuf[]中前50个字节发送给用户

    //返回值大于0表示写入的字节数;

    //返回值等于0表示没有写入任何数据;

    //返回值小于0表示写入失败

    if(retvalue < 0)

    {

      printf("write file %s failed!\r\n", filename);

    }

  }

  /* 关闭设备 */

  retvalue = close(fd);

  //fd表示要关闭的“文件描述符”

  //返回值等于0表示关闭成功

  //返回值小于0表示关闭失败

  if(retvalue < 0)

  {

    printf("Can't close file %s\r\n", filename);

    return -1;

  }

  return 0;

}

6、编写脚本文件

CharDeviceXXX_APP.sh文件内容如下:

#!/bin/sh

file="CharDeviceXXX_APP"

if [ -s $file ]

#"-s $file"检测文件是否为空(文件大小是否大于0),不为空返回 true

then

rm CharDeviceXXX_APP

echo clear CharDeviceXXX_APP

else

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

echo CharDeviceXXX_APP

fi

7、测试

输入“make回车”编译生成“CharDeviceXXX.ko

输入“chmod 777 CharDeviceXXX_APP.sh回车

将“CharDeviceXXX_APP.sh”赋可执行权限

输入“./CharDeviceXXX_APP.sh回车”,编译生成“CharDeviceXXX_APP”文件

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

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

输入“root

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

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

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

输入“ls”查看“CharDeviceXXX.ko和CharDeviceXXXApp”是否存在

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

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

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

输入“mknod /dev/CharDeviceXXX c 200 0回车

//“mknod”是创建节点命令

//“/dev/CharDeviceXXX”表示节点文件

//“c”表示CharDeviceXXX是个字符设备

//“200”表示设备的主设备号

//“0”表示设备的次设备号

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

输入“./CharDeviceXXX_APP /dev/CharDeviceXXX 1回车”执行读操作

输入“./CharDeviceXXX_APP /dev/CharDeviceXXX 2回车”执行写操作

输入“cat /proc/devices回车”查询设备号

操作完成,则执行卸载模块:

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

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

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

至此,CharDeviceXXX设备的整个驱动就验证完成了,驱动工作正常。

8、修改Makefile文件如下:

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

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

CURRENT_PATH := $(shell pwd)

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

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

obj-m := CharDeviceXXX.o

#生成“obj-m”需要依赖“CharDeviceXXX.o”

ko: kernel_modules

#生成“build”需要依赖“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/CharDeviceXXX”

@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_ko:

$(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 CharDeviceXXX_APP.c -o CharDeviceXXX_APP

clean_app:

rm CharDeviceXXX_APP

9、使用Makefile编译

输入“rm CharDeviceXXX_APP.sh回车

输入“make clean_ko回车”,清除CharDeviceXXX.*

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

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

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

输入“ls -l回车

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

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

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

相关文章

DC-2靶机详解

写写自己打DC-2的过程 使用工具 kali DC-2的靶机下载地址为&#xff1a;https://www.vulnhub.com/entry/dc-2,311/ 环境配置。 Kali和DC-2都设置为NAT模式&#xff0c;都为仅主机模式也可以。 信息收集 arp-scan -l nmap -sn 192.168.236.0/24 获取靶机ip&#xff1a;192.16…

最小高度树-力扣(Leetcode)

题目链接 最小高度树 思路&#xff1a;本质上是找到树中的最长路径。当最长路径上中间点&#xff08;若路经长为偶数&#xff0c;则中间点仅有一个&#xff0c;否者中间点有两个&#xff09;作为根时&#xff0c;此时树高最小。 Code: class Solution { public://拓扑排序int…

Neoverse CSS N3:实现市场领先能效的最快途径

区分老的架构 从云到边缘&#xff0c;Arm Neoverse 提供无与伦比的性能、效率、设计灵活性和 TCO 优势&#xff0c;正在颠覆传统基础设施芯片。 我们看到云和超大规模服务运营商正在推动更高的计算密度。随着 128 核心 CPU 设计上市&#xff08;Microsoft Cobalt、阿里巴巴 Y…

使用 Docker 部署 MrDoc 在线文档管理系统

1&#xff09;MrDoc 介绍 MrDoc 简介 MrDoc 觅思文档&#xff1a;https://mrdoc.pro/ MrDoc 使用手册&#xff1a;https://doc.mrdoc.pro/p/user-guide/ MrDoc 可以创建各类私有化部署的文档应用。你可以使用它进行知识管理、构建团队文库、制作产品手册以及在线教程等。 Mr…

Git推送本地仓库至阿里云仓库

Git推送本地仓库至阿里云仓库 1.安装Git 参考Git安装详解 2.生成 SSH 密钥 基于RSA算法SSH 密钥 1.管理员权限运行Git Bash 2.输入生成密钥指令点击回车&#xff0c;选择 SSH 密钥生成路径。 $ ssh-keygen -t rsa -C "2267521563qq.com"3.以 RSA算法为例&…

jupyter 一键快捷启动方法研究

1.效果 首先打开dat 文件&#xff0c;同意赋予管理员 输入序号1 成功启动 2.Bat代码 %1 mshta vbscript:CreateObject("Shell.Application").ShellExecute("cmd.exe","/c %~s0 ::","","runas",1)(window.close)&&e…

(十)SpringCloud系列——openfeign的高级特性实战内容介绍

前言 本节内容主要介绍一下SpringCloud组件中微服务调用组件openfeign的一些高级特性的用法以及一些常用的开发配置&#xff0c;如openfeign的超时控制配置、openfeign的重试机制配置、openfeign集成高级的http客户端、openfeign的请求与响应压缩功能&#xff0c;以及如何开启…

2.模拟问题——5.星期几与字符串对应

输入输出示例 输入&#xff1a; 9 October 2001 14 October 2001 输出&#xff1a; Tuesday Sunday 【原题链接】 字符串处理 C风格的字符串 字符数组&#xff0c;以’\0‘结尾建议在输入输出语句中使用 C风格的字符串 #include <string> using namespace std;初始化…

Electron通过预加载脚本从渲染器访问Node.js

问题&#xff1a;如何实现输出Electron的版本号和它的依赖项到你的web页面上&#xff1f; 答案&#xff1a;在主进程通过Node的全局 process 对象访问这个信息是微不足道的。 然而&#xff0c;你不能直接在主进程中编辑DOM&#xff0c;因为它无法访问渲染器 文档 上下文。 它们…

FreeRTOS学习笔记——FreeRTOS中断管理

精华总结&#xff1a; 中断优先级0为最高&#xff0c;任务优先级0为最低 中断优先级分组中为方便rtos管理4bit全部设置成抢占优先级 32单片机的中断管理是由3个寄存器完成&#xff08;名字忽略&#xff0c;具体功能忽略&#xff09;&#xff0c;三个寄存器都是32bit&#xff0c…

【JavaScript】手写 Promise(静态方法)

手写Promise-静态方法resolve 需求: 返回一个带有成功原因的Promise对象 HMPromise.resolve(new HMPromise((resolve, reject) > {// resolve(resolve)// reject(reject)// throw error })).then(res > {console.log(res:, res) }, err > {console.log(err:, err) …

Google Dremel和parquet的复杂嵌套数据结构表征方法解析

转载请注明出处。作者&#xff1a;archimekai 核心参考文献&#xff1a; Dremel: Interactive Analysis of Web-Scale Datasets 文章目录 引言复杂嵌套数据结构的无损表征问题Dremel论文中提出的表征方法parquet备注 引言 Dremel是Google的交互式分析系统。Google大量采用prot…