字符设备驱动-程序员宅基地

技术标签: linux  

字符设备框架搭建

(1)创建工程

       新建驱动开发项目文件目录

1、添加头文件路径

        因为是编写 Linux 驱动,因此会用到 Linux 源码中的函数。我们需要在 VSCode 中添加 Linux
源码中的头文件路径。打开 VSCode,按下“Crtl+Shift+P”打开 VSCode 的控制台,然后输入
C/C++: Edit configurations(JSON) ”,打开 C/C++编辑配置文件

打开以后会自动在.vscode 目录下生成一个名为 c_cpp_properties.json 的文件

对该文件进行修改,修改后的c_cpp_properties.json 为

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/f3161/linux/IMX6ULL/linux/include", 
                "/home/f3161/linux/IMX6ULL/linux/arch/arm/include", 
                "/home/f3161/linux/IMX6ULL/linux/arch/arm/include/generated/"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

其中 includPath内为内核目录,修改成自己具体的目录

2、Makefile文件

新建Makefile文件,输入内容如下

KERNELDIR := /home/f3161/linux/IMX6ULL/linux
CURRENT_PATH := $(shell pwd)
obj-m := xxx.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第一行为内核路径

第三行是生成的.ko文件名,需要与.c文件对应。

(2)编写驱动程序xxx.c

1、头文件

xxx.c头文件(包含了后续所有常见实验的头文件在内)如下

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fcntl.h>

#define XXX_CNT 1 //设备号个数
#define XXX_NAME "xxx" //设备号名字

2、注册驱动

 /* 驱动入口函数 */
static int __init xxx_init(void)
{
int ret = 0;
return ret;
 }
 /* 驱动出口函数 */
static void __exit xxx_exit(void)
 {
 }
module_init(xxx_init);    
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FANG");

注意驱动入口出口函数的参数。

3、添加设备结构体

//设备结构体
struct xxx_dev{
   dev_t devid;//设备号
   int major;//主设备号
   int minor;//次设备号
   struct cdev cdev; //字符设备
   struct class *class;//类
   struct device *device;//设备
   struct device_node *nd;//设备树节点
   int xxx_gpio; //GPIO编号
};
struct xxx_dev xxx;

devid:每个设备都有一个设备号,设备号由主设备号(major)和次设备号两部分组成

cdev:用来注册字符设备

class,device:来于自动创建设备节点

xxx_gpio:        设备所用的GPIO编号(设备树需要)       

4、申请设备号

在入口函数static int __init xxx_init(void)中添加

   xxx.major = 0;
   if(xxx.major){ //给定设备号
      xxx.devid = MKDEV(xxx.major, 0);
      ret = register_chrdev_region(xxx.devid, DTSLED_CNT, XXX_NAME);
   }else{ // 申请设备号
      ret = alloc_chrdev_region(&xxx.devid, 0, DTSLED_CNT, XXX_NAME);
      xxx.major = MAJOR(xxx.devid);
   }
   if(ret<0){
      goto fail_devid;
   }

其中,DTSLED_CNT表示设备号个数,需要.c文件开头添加宏 #define DTSLED_CNT 1

        XXX_NAME表示设备号名字,添加宏 #define XXX_NAME "xxx"

        MKDEV用于将给定的主设备号和次设备号的值(这里是0)组合成 dev_t 类型的设备号。

        register_chrdev_region函数使用给定的主设备号和次设备号注册设备号

        函数原型:int register_chrdev_region(dev_t from, unsigned count, const char *name)
         参数 from 是要申请的起始设备号,也就是给定的设备号;参数 count 是要申请的数量,一般都是一个;参数 name 是设备名字。

        alloc_chrdev_region函数是在没有指定设备号时申请设备号

        函数原型:int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

        参数dev是申请到的设备号baseminor是次设备号起始地址(一般用0),count和name同上。

如果申请失败,则goto fail_devid(后面提及)

在驱动出口函数static void __exit disled_exit(void)中,需要释放设备号

unregister_chrdev_region(xxx.devid, DTSLED_CNT);

5、添加字符设备操作集

static const struct file_operations xxx_fops = {
   .owner = THIS_MODULE,
   .write = xxx_write,
   .open = xxx_open,
   .read = xxx_read,
   .release = xxx_release,
};

添加字符设备操作集对应的函数

static int xxx_open(struct inode *inode, struct file *filp){
      filp->private_data = &xxx;
      return 0;
}
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count,loff_t *ppos){
   struct xxx_dev *dev = (struct xxx_dev*)filp->private_data;
   return 0;
}
static int xxx_release(struct inode *inode, struct file *filp){
      struct xxx_dev *dev = (struct xxx_dev*)filp->private_data;
      return 0;
}
static ssize_t xxx_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
 {
    struct xxx_dev *dev = (struct xxx_dev *)filp->private_data;
    return 0;
}

其中设置了文件的私有属性,将设备结构体作为私有数据添加到设备文件中。在上述函数中使用dev变量代替xxx_dev

xxx_write函数

xxx_write函数表示用户空间(应用程序)传递给内核(驱动程序)的数据并且打印出来,因为用户空间内存不能直接访问内核空间的内存,所以需要借助函数 copy_from_user 将用户空间的数据复制到 writebuf 这个内核空间中。

函数原型:

static inline long copy_from_user(void __user *to, const void *from, unsigned long n)

参数 to 表示目的(驱动程序数据),参数 from 表示源(应用程序数据),参数 n 表示要复制的数据长度。如果复制成功,返回值为 0,如果复制失败则返回负数。

所以修改后的xxx_write()函数:——>将应用程序中的数据传递给驱动程序

static char writebuf[100];
static ssize_t xxx_write(struct file *filp,const char __user *buf, size_t cnt, loff_t *offt)
 {
    int retvalue = 0;/* 接收用户空间传递给内核的数据并且打印出来 */
    retvalue = copy_from_user(writebuf, buf, cnt);
    if(retvalue == 0){
        printk("kernel recevdata:%s\r\n", writebuf);
    }else{
        printk("kernel recevdata failed!\r\n");
    }
    return 0;
}
xxx_read函数

xxx_read函数表示内核(驱动程序)向用户空间(应用程序)传递的数据 ,因为内核空间不能直接操作用户空间的内存,因此需要借助 copy_to_user 函数来完成内核空间的数据到用户空间的复制。

函数原型

static inline long copy_to_user(void __user *to, const void *from, unsigned long n)

 参数 to 表示目的(应用程序数据),参数 from 表示源(驱动程序数据),参数 n 表示要复制的数据长度。如果复制成功,返回值为 0,如果复制失败则返回负数。

所以修改后的xxx_read()函数:——>将驱动程序中的kerneldata变量的数据传递给应用程序

static char kerneldata[] = {"kernel data!"};
static char readbuf[100];
static ssize_t xxx_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    retvalue = copy_to_user(buf, readbuf, cnt);
    if(retvalue == 0){
        printk("kernel senddata ok!\r\n");
    }else{
        printk("kernel senddata failed!\r\n");
    }
    return 0;
}

6、注册字符设备

在入口函数static int __init xxx_init(void)中添加

   xxx.cdev.owner = THIS_MODULE;
   cdev_init(&xxx.cdev, &xxx_fops);
   ret = cdev_add(&xxx.cdev, xxx.devid, XXX_CNT);
   if(ret<0){
    goto fail_cdev;
}
     

cdev_init函数对字符设备进行初始化。

函数原型:void cdev_init(struct cdev *cdev, const struct file_operations *fops)

参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合(标题5中内容)。

cdev_add函数用于向 Linux 系统添加字符设备(cdev 结构体变量)。

函数原型:int cdev_add(struct cdev *p, dev_t dev, unsigned count)

参数 p 指向要添加的字符设备(cdev 结构体变量),参数 dev 就是设备所使用的设备号,参
数 count 是要添加的设备数量。

:如果字符设备注册失败,则goto fail_cdev

在驱动出口函数static void __exit disled_exit(void)中,需要删除字符设备

cdev_del(&xxx.cdev);

7、自动创建设备节点

        设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点。

需要在入口函数static int __init xxx_init(void)中创建设备节点,

   xxx.class = class_create(THIS_MODULE,XXX_NAME);
   if(IS_ERR(xxx.class)){
      ret = PTR_ERR(xxx.class);
      goto fail_class;
   }
   xxx.device = device_create(xxx.class, NULL, xxx.devid, NULL, XXX_NAME);
      if(IS_ERR(xxx.device)){
      ret = PTR_ERR(xxx.device);
      goto fail_device;
   }

class_create函数用于创建类

函数原型:struct class *class_create (struct module *owner, const char *name)

参数 owner 一般为 THIS_MODULE,参数 name 是类名字。

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

:如果类创建失败,需要goto _class

在驱动出口函数static void __exit disled_exit(void)中,需要摧毁类

class_destroy(xxx.class);

device_create函数用于创建设备

函数原型:struct device *device_create(struct class *class,
                                                                struct device *parent,
                                                                dev_t devt,
                                                                void *drvdata,
                                                                const char *fmt, ...)

        参数 class 就是设备要创建哪个类;

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

        参数 devt 是设备号;

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

        参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件;

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

:如果创建失败,则goto fail_device

在驱动出口函数static void __exit disled_exit(void)中,需要摧毁设备

device_destroy(xxx.class, xxx.devid);

8、创建失败goto和return

在入口函数static int __init xxx_init(void)最后中添加goto内容

   return 0;
   fail_rs:
   fail_findnd:
      device_destroy(xxx.class, xxx.devid);
   fail_device://创建设备失败
      class_destroy(xxx.class);
   fail_class://创建类失败
      cdev_del(&xxx.cdev);
   fail_cdev://字符设备失败
      unregister_chrdev_region(xxx.devid, XXX_CNT);
   fail_devid://设备号失败
      return ret;

        如果创建成功,则返回 0,否则返回ret为负数,其中各种goto顺序不能乱,比如如果申请字符设备失败,说明设备号已经申请成功,需要释放设备号;如果创建类失败,说明字符设备和设备号创建成功,需要释放。代码依次执行。

9、驱动出口函数编写

该函数中需要释放字符设备,设备号,类,设备

static void __exit xxx_exit(void){
   //删除设备
   cdev_del(&xxx.cdev);
   //释放设备号
   unregister_chrdev_region(xxx.devid, XXX_CNT);
   //摧毁设备
   device_destroy(xxx.class, xxx.devid);
   //摧毁类
   class_destroy(xxx.class);
}

函数原型:void cdev_del(struct cdev *p)
参数 p 就是要删除的字符设备。

函数原型是:void unregister_chrdev_region(dev_t from, unsigned count)
参数from:要释放的设备号。count:表示从 from 开始,要释放的设备号数量。

函数原型:void device_destroy(struct class *class, dev_t devt)

参数 class 是要删除的设备所处的类,参数 devt 是要删除的设备号。(在驱动出口函数中,需要先摧毁设备,再摧毁类)

函数原型:void class_destroy(struct class *cls);
参数 cls 就是要删除的类。

10、xxx.c整体代码

xxx.c文件

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>

#define XXX_CNT 1 //设备号个数
#define XXX_NAME "xxx" //设备号名字
//设备结构体
struct xxx_dev{
   dev_t devid;//设备号
   int major;//主设备号
   struct cdev cdev; //字符设备
   struct class *class;//类
   struct device *device;//设备
   struct device_node *nd;//设备树节点
   int    xxx_gpio; //GPIO编号
};
struct xxx_dev xxx;

static int xxx_open(struct inode *inode, struct file *filp){
      filp->private_data = &xxx;
      return 0;
}
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){
   struct xxx_dev *dev = (struct xxx_dev*)filp->private_data;
   return 0;
}
static int xxx_release(struct inode *inode, struct file *filp){
      struct xxx_dev *dev = (struct xxx_dev*)filp->private_data;
      return 0;
}
static ssize_t xxx_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
 {
    struct xxx_dev *dev = (struct xxx_dev *)filp->private_data;
    return 0;
}
static const struct file_operations xxx_fops = {
   .owner = THIS_MODULE,
   .write = xxx_write,
   .open = xxx_open,
   .release = xxx_release,
   .read = xxx_read,
};
//驱动入口函数
static int __init xxx_init(void){
   int ret = 0;
   //注册设备号
   xxx.major = 0;
   if(xxx.major){ //给定设备号
      xxx.devid = MKDEV(xxx.major, 0);
      ret = register_chrdev_region(xxx.devid, XXX_CNT, XXX_NAME);
   }else{ // 申请设备号
      ret = alloc_chrdev_region(&xxx.devid, 0, XXX_CNT, XXX_NAME);
      xxx.major = MAJOR(xxx.devid);
   }
   if(ret<0){
      goto fail_devid;
   }
   //添加字符设备
   xxx.cdev.owner = THIS_MODULE;
   cdev_init(&xxx.cdev, &xxx_fops);
   ret = cdev_add(&xxx.cdev, xxx.devid, XXX_CNT);
   if(ret<0)
      goto fail_cdev;
   //自动创建设备节点
   xxx.class = class_create(THIS_MODULE,XXX_NAME);
   if(IS_ERR(xxx.class)){
      ret = PTR_ERR(xxx.class);
      goto fail_class;
   }
   xxx.device = device_create(xxx.class, NULL,xxx.devid, NULL, XXX_NAME);
      if(IS_ERR(xxx.device)){
      ret = PTR_ERR(xxx.device);
      goto fail_device;
   }
   
   return 0;
   fail_device:
      class_destroy(xxx.class);
   fail_class:
      cdev_del(&xxx.cdev);
   fail_cdev:
      unregister_chrdev_region(xxx.devid, XXX_CNT);
   fail_devid:
      return ret;
}
//驱动出口函数
static void __exit xxx_exit(void){
   //删除设备
   cdev_del(&xxx.cdev);
   //释放设备号
   unregister_chrdev_region(xxx.devid, XXX_CNT);
   //摧毁设备
   device_destroy(xxx.class, xxx.devid);
   //摧毁类
   class_destroy(xxx.class);
}
//注册驱动
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FANG");

(3)应用程序编写

编写xxx.c文件对应的应用程序代码文件xxxApp.c,用于读取设备驱动文件

1、头文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"

2、  main函数      

int main(int argc, char *argv[]){ 
return 0;
}

其中,argc是应用程序参数个数

          argv是具体的参数内容(字符串形式)

3、open函数

    int fd = 0,ret=0;
    char *filename;
    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("can not open file %s\n",filename);
        return -1;
    }

open()函数原型为 int open(const char *pathname, int flags);

参数 pathname:要打开的设备或者文件名,这里是第二个参数argv[1]
        flags:文件打开模式,以下三种模式必选其一:
                O_RDONLY         只读模式
                O_WRONLY        只写模式
                O_RDWR             读写模式

        如果文件打开成功的话返回文件的文件描述符(int),即fd,后续代码通过这个文件描述符完成对文件的操作。

4、read函数

char readbuf[100];//从驱动读取到的数据存到readbuf中
ret = read(fd, radbuf, 10);
if(ret < 0){
    printf("read file %s failed!\n", filename);
    return -1;
}

read() 函数原型:ssize_t read(int fd, void *buf, size_t count)

参数  fd:要读取的文件描述符,读取文件之前要先用 open 函数打开文件,open 函数打开文件成
功以后会得到文件描述符。
        buf:数据读取到此 buf 中。
        count:要读取的数据长度,也就是字节数。
        返回值:读取成功的话返回读取到的字节数;如果返回 0 表示读取到了文件末尾;如果返
回负值,表示读取失败。

5、write函数

char writebuf[100];//应用程序向驱动写的数据
ret = write(fd,writebuf,sizeof(writebuf));
    if(ret<0){
        printf("write file %s failed!\n" ,filename);
        close(fd);
        return -1;
    }

write()函数原型:ssize_t write(int fd, const void *buf, size_t count);
参数  fd:要进行写操作的文件描述符,写文件之前要先用 open 函数打开文件,open 函数打开文
件成功以后会得到文件描述符。
         buf:要写入的数据。
         count:要写入的数据长度,也就是字节数。
         返回值:写入成功的话返回写入的字节数;如果返回 0 表示没有写入任何数据;如果返回负值,表示写入失败。

6、close函数

close(fd);

 close() 函数原型:int close(int fd);
参数: fd:要关闭的文件描述符。
            返回值:0 表示关闭成功,负值表示关闭失败。

(4)程序测试

1、驱动程序编译

驱动程序文件xxx.c目录下终端运行make编译,生成xxx.ko文件

make

已知:开发版根文件系统通过NFS挂载到ubuntu目录下。

将编译得到的xxx.ko文件拷贝到ubuntu的挂载目录nfs下的lib/modules/4.1.15/中

cp xxx.ko /home/f3161/linux/nfs/lib/modules/4.1.15/ -f

2、应用程序编译

对应用程序xxxApp.c进行编译,生成xxxApp文件

arm-linux-gnueabihf-gcc xxxApp.c -o xxxApp

将编译得到的xxxApp文件拷贝到ubuntu的挂载目录nfs下的lib/modules/4.1.15/中

cp xxxApp /home/f3161/linux/nfs/lib/modules/4.1.15/ -f

3、开发板终端运行

在windows电脑下使用putty软件与开发板串口通信,作为串口终端使用。

第一次加载驱动的时候需要运行命令depmod

depmod

运行modprobe xxx.ko命令加载驱动

modprobe xxx.ko

输入“lsmod”命令即可查看当前系统中存在的模块 

lsmod

输入命令查看当前系统中的设备

cat /proc/devices

加载应用程序,其中后面可以添加参数

./xxxApp /dev/xxx

卸载驱动

rmmod xxx.ko

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/F1602609101/article/details/136761707

智能推荐

序列模型(RNN-GRU-LSTM-BRNN-Deep RNN)_brnn 向量 序列-程序员宅基地

文章浏览阅读5.4k次,点赞2次,收藏16次。LSTM原理,应用。从循环神经网络(Recurrent Neural Network,RNN)可以通过许多不同的方式建立,但就像几乎所有函数都可以被认为是前馈网络,基本上任何涉及循环的函数可以被认为是一个循环神经网络。它的基本结构以及其展开的理解如下图所示: 同一网络被视为展开的计算图,其中每个节点现在与一个特定的时间实例相关联................_brnn 向量 序列

什么是深度学习,深度学习和机器学习有什么关系?_深度学习,是机器学习的一种特定技术,称其为深度,是因为他有()结构。-程序员宅基地

文章浏览阅读3.2k次。深度学习的概念源于人工神经网络的研究。含多隐层的多层感知器就是一种深度学习结构。深度学习通过组合低层特征形成更加抽象的高层表示属性类别或特征,以发现数据的分布式特征表示。晦涩难懂的概念,略微有些难以理解,但是在其高冷的背后,却有深远的应用场景和未来。深度学习是实现机器学习的一种方式或一条路径。其动机在于建立、模拟人脑进行分析学习的神经网络,它模仿人脑的机制来解释数据。比如其按特定的物理距离连接;..._深度学习,是机器学习的一种特定技术,称其为深度,是因为他有()结构。

bootstrap tab切换后,刷新 页面 回到被选中的tab页签_bootstrap刷新页面-程序员宅基地

文章浏览阅读686次。描述:当使用bootstrap 的tab 进行页面切换后,一刷新页面,又回到了第一个初始页,从而又得重新切换,实现目标:例如当前切换到第三个页面后,刷新页面自动定位到第三个tab页面中。_bootstrap刷新页面

uniapp视频播放器(h5+app)

这是关于一篇在uniapp使用video视频播放器,支持自定义播放器样式,支持手势操作,选集、倍数和清晰度切换,支持SRT字幕格式。

uCosii从任务的建立到运行_ucosii用户任务中都有参数初始化,先执行哪个-程序员宅基地

文章浏览阅读823次。μC/OS-Ⅱ从任务的建立到运行提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、任务建立到运行整体流程二、分步功能实现1.μC/OS-Ⅱ初始化2.读入数据总结前言最近在学习ucosii实时操作系统,记录下学习过程,以待日后查阅.tips:本文所用ucosii版本为V2.86,硬件平台为STM32.一、任务建立到运行整体流程μC/OS-Ⅱ任务从建立到运行的整体流程如下所述:1. μC/OS-Ⅱ初始化: OSInit();2. 创建任务: Create_T_ucosii用户任务中都有参数初始化,先执行哪个

【燃料电池】燃料电池并网以最大额定功率运行研究(Simulink实现)_如何实现燃料电池大规模并网-程序员宅基地

文章浏览阅读260次。控制和电流谐振控制方法,增强了系统的稳定性,提高了供电质量。部分文献针对风力发电、太阳电池并网系统的小干扰稳定性进行研究,对燃料电池发电系统稳定性分析具有一定指导意义。文献[6,7]则分别建立太阳电池和风力发电系统的小信号模型,利用特征值分析法对系统的小干扰稳定性进行分析。文献[8]在光伏发电系统控制器参数进行全局优化以提高系统稳定性,但只考虑特征值作为单一优化目标,没有考虑阻尼比对系统稳定的影响,影响了控制参数的优化效果。在该模型中,燃料电池连接到电网,并由基于功率的控制器控制。行百里者,半于九十。_如何实现燃料电池大规模并网

随便推点

逆向_base64_rc4_----笔记_mdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmd-程序员宅基地

文章浏览阅读1k次。新人…学校比赛,没什么经验,上去见见世面某公司的月赛题 资源就不放了,只是当笔记Ida打开 有点小陷阱 巧妙的堆栈运用导致载入ida分析不了 得不到函数的边界比较幸运win32的程序od打开走一遍流程感受下00AD12E7 . 52 push edx00AD12E8 . 68 C821AD00 push 5ba358a4.00AD21C8 ..._mdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdawmdaw

致 Tapdata 开源贡献者:聊聊 2022 年的进展和新一年的共建计划-程序员宅基地

文章浏览阅读781次。在内、外部开发者的合力之下,过去一年 Tapdata 新增数据源近20个,实现了60+数据源的接入能力。一个人可以走得很快,一群人可以走得更远,2023年期待与您共创更受欢迎的开源项目。_tapdata

python列表添加元素的三种方法定义集合数据对象_python 学习第三天 可迭代对象(列表,字典,元组和集合)...-程序员宅基地

文章浏览阅读642次。列表,字典,元组和集合列表 list列表是由一系列特定元素组成的,元素和元素之间没有任何关联关系,但他们之间有先后顺序关系列表是一种容器列表是序列的一种列表是可以被改变的序列Python中的序列类型简介(sequence)字符串(str) 列表(list) 元组(tuple) 字节串(bytes) 字节数组(bytearray)创建空列表的字面值L = [ ] # L绑定空列表创建非空列表: L ..._python 集合 对象元素

第十五周 项目 1 - 验证算法_insertht(hashtable ha[], int& n, int m, int p, key-程序员宅基地

文章浏览阅读316次。/* *Copyright (c) 2016,烟台大学计算机学院 *All right reserved. *文件名称:test.cpp *作者:杨天瑞 *完成日期:2016年12月16日 *版本号:v1.7.5 * * 问题描述:验证算法。 * 程_insertht(hashtable ha[], int& n, int m, int p, keytype k)

react-native之项目结构分析_react native课程的项目分析-程序员宅基地

文章浏览阅读6k次,点赞5次,收藏10次。前言庖丁为文惠君解牛,手之所触,肩之所倚,足之所履,膝之所踦,砉然响然,奏刀騞然,莫不中音。合于桑林之舞,乃中经首之会。熟悉项目的结构,是开发的基本也是技术提升的一个重要途径,现总结下自己对react-native项目结构的分析与理解。正文项目结构init 的一个项目结构如图:tests:测试文件夹,执行命令 “npm test”会调用此文件夹,在文件夹中需要引入待测试文件。android:An_react native课程的项目分析

uniapp开发公众号,微信开发者工具进行本地调试_uniapp使用微信开发者工具 利用公众号调试的方法-程序员宅基地

文章浏览阅读584次。uniapp开发公众号,微信开发者工具进行本地调试_uniapp使用微信开发者工具 利用公众号调试的方法