内核定时器API实现点灯

news2024/11/24 17:09:29
1.内核定时器        

       定时器是一个很常用的功能,需要周期性处理的工作都要用到定时器。 Linux 内核定时器
采用系统时钟来实现,并不是6ull里面的硬件定时器。 Linux 内核定时器使用很简单,只需要提供超时时间(相当于定时值)和定时处理函数即可,当超时时间到了以后设置的定时处理函数就会执行,和我们使用硬件定时器的套路一样,只是使用内核定时器不需要做一大堆的寄存器初始化工作。在使用内核定时器的时候要注意一点,内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。
        Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中,定义如下:

struct timer_list {
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    struct list_head entry;
    unsigned long expires;                 /* 定时器超时时间,单位是节拍数 */
    struct tvec_base *base;
 
    void (*function)(unsigned long);       /* 定时处理函数 */
    unsigned long data;                    /* 要传递给 function 函数的参数 */
 
    int slack;
 
#ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

 =========================

 2.内核定时器API函数    

      要使用内核定时器首先要先定义一个 timer_list 变量,表示定时器, tiemr_list 结构体的
expires 成员变量表示超时时间,单位为节拍数。比如我们现在需要定义一个周期为 2 秒的定时
器,那么这个定时器的超时时间就是 jiffies+msecs_to_jiffies(2000),function 就是定时器超时以后的定时处理函数,当定时时间到了以后,就会跳转到function执行。

        定义好定时器后,还需要API函数(定义在linux/timer.h)来初始化定时器:

        ①、init_timer函数
        init_timer 函数负责初始化 timer_list 类型变量,函数原型:

#define init_timer(timer)                        \
    __init_timer((timer), 0)
 
#define __init_timer(_timer, _flags)                    \
    init_timer_key((_timer), (_flags), NULL, NULL)
 
void init_timer_key(struct timer_list *timer, unsigned int flags,
            const char *name, struct lock_class_key *key)
    timer:要初始化的定时器。

   

        ②、add_timer函数
         用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行,函数原型如下:

extern void add_timer(struct timer_list *timer);
timer:要初始化的定时器。

        ③、del_timer函数
        用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出,函数原型:

extern int del_timer(struct timer_list * timer);
         timer:要初始化的定时器。
返回值:0,定时器没被激活,1,定时已经激活。

        ④、del_timer_sync函数
        函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中。函数原型:

extern int try_to_del_timer_sync(struct timer_list *timer);
timer:要初始化的定时器。

返回值:0,定时器没被激活,1,定时已经激活。

        ⑤、mod_timer函数
        用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!函数原型如下:

extern int mod_timer(struct timer_list *timer, unsigned long expires);
timer:要修改超时时间的定时器。

expires:修改后的超时时间。

返回值:0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活。

 ===============================

  3.内核定时器的使用流程
struct timer_list timer; /* 定义定时器 */
 
/* 定时器回调函数 */
void function(unsigned long arg)
{
    /*
     * 定时器处理代码
     */
 
    /* 如果需要定时器周期性运行的话就使用 mod_timer
     * 函数重新设置超时值并且启动定时器。
     */
 
    mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));    /* 修改超时时间为2s */
}
 
/* 初始化函数 */
void init(void)
{
    init_timer(&timer); /* 初始化定时器 */
 
    timer.function = function; /* 设置定时处理函数 */
    timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
    timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
 
    add_timer(&timer); /* 启动定时器 */
}
 
/* 退出函数 */
 
void exit(void)
{
    del_timer(&timer); /* 删除定时器 */
    /* 或者使用 */
    del_timer_sync(&timer);
}
4.Linux内核短延时函数


        有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。 Linux 内核提供了毫秒、微秒和纳秒延时函数,如表:

函数描述

void ndelay(unsigned long nsecs)  
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)
//  ms、us、ns延时函数


unlocked_ioctl和compat_ioctl
        函数简介:
        unlocked_ioctl\compat_ioctl是file_operation 结构体中的两个函数

        unlocked_ioctl函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。简单点来说,当用户空间应用程序调用 ioctl函数向驱动发送控制信息,驱动程序会执行unlocked_ioctl这个函数

long (*unlocked_ioctl) (struct file *filep, unsigned int cmd, unsigned long arg);
        filep:设备文件名。
        cmd:应用程序发送过来的命令信息。后面我们会仔细说一下这个CMD命令如何创建。
        arg:应用程序发过来的参数。

         compat_ioctl函数的功能与unlocked_ioctl函数一样,区别在于64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。

long (*compat_ioctl) (struct file *filep, unsigned int cmd, unsigned long arg);

===========================


     5.ioctl函数CMD命令


        在linux内核中有帮助手册:linux/Documentation/ioctl/ioctl-decoding.txt这个文档中有介绍CMD这个命令:

#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

#define _IOC(dir,type,nr,size) \
    (((dir)  << _IOC_DIRSHIFT) | \     //dir(读写)方向左移30位
     ((type) << _IOC_TYPESHIFT) | \    //type类型左移8位
     ((nr)   << _IOC_NRSHIFT) | \      //nr功能左移0位
     ((size) << _IOC_SIZESHIFT))      // size传递数据大小左移16位
 /**通过分析是dir、type、nr、size几个数都左移了一个不知道的宏的位数,通过查找发现这些宏如下,
 所以得到上边每行注释左移位数**/
#define _IOC_NRBITS 8                                                                                   
#define _IOC_TYPEBITS   8
# define _IOC_SIZEBITS  14
#define _IOC_NRSHIFT    0
#define _IOC_TYPESHIFT  (_IOC_NRSHIFT+_IOC_NRBITS)  // 0+8 = 8                                                     
#define _IOC_SIZESHIFT  (_IOC_TYPESHIFT+_IOC_TYPEBITS) //8+8 = 16
#define _IOC_DIRSHIFT   (_IOC_SIZESHIFT+_IOC_SIZEBITS) //16+14 = 30

总结:我们通过分析_IOC这个宏可以发现他做了这么一件事,将一个32位的数拆成了四个部分,分别是dir、type、nr、size,分别如下解释和图示:
bit31~bit30:“区别读写” 区,作用是区分是读取命令还是写入命令;
bit29~bit16:“数据大小” 区,表示 ioctl() 中的 arg 变量传送的内存大小。
bit15~bit8 : “ 魔数” (也称为"幻数")区,这个值用以与其它设备驱动程序的 ioctl 命令进行区别。
bit7~bit0 :“区别序号”区,是区分命令的命令顺序序号
 

                            

这个CMD是一个32位的。31~30位是方向位,_IOR是向驱序读,_IOW是向驱动写。29~16位是用户空间向内核空间传输控制信息的数据大小,15~8位表示类型,驱动的标识位,一个特殊字符(ASCII)代表不同的一个驱动。7~0位就是不同的控制功能。

===========================

内核代码:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/errno.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/semaphore.h>
#include <asm/ioctls.h>
 
 
/* 设备名称和个数 */
#define TIMER_CNT           1
#define TIMER_NAME          "timer"
 
  
/* 命令宏 */
#define OPEN_CMD            _IO('E', 1)
#define CLOSE_CMD           _IO('E', 2)
#define SET_PERIOD_CMD      _IOW('E', 3, int)    
 
 
 
/* timer结构体 */
typedef struct timer_dev {
    dev_t devid;                        /* 设备号 */
    int major;                          /* 主设备号 */
    int minor;                          /* 次设备号 */
    struct cdev dev;                    /* 设备 */
    struct class *class;                /* 类 */
    struct device *device;              /* 类的设备 */
    struct device_node  *nd;             /* 设备树节点 */
    int led_gpio;                       /* LED的GPIO编号 */
    struct timer_list timer;            /* 定时器 */
 
    int timerperiod;                    /* 定时器周期 */
    spinlock_t lock;                    /* 自旋锁 */
 
}timer_dev;
timer_dev timer;
 
static int timer_open (struct inode *inode, struct file *filep)
{
 
    filep->private_data = &timer;       /* 设置私有数据 */
 
    timer.timerperiod = 500;           /* 设置定时时间为1s */
 
    return 0;
}
 
 
static long timer_unlocked_ioctlioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
    int ret = 0;
    timer_dev *dev = filep->private_data;    /* 获取私有数据 */
    unsigned int timerperod = 0;
    unsigned long flag = 0;
    unsigned long value = 0;
 
	switch (cmd) {
	    case OPEN_CMD:         /*  打开定时器 */
 
            spin_lock_irqsave(&dev->lock,flag);        /* 自锁 */
            timerperod = dev->timerperiod;
            spin_unlock_irqrestore(&dev->lock,flag);   /* 解锁 */
            mod_timer(&dev->timer,jiffies + msecs_to_jiffies(timerperod));
            break;
	    case CLOSE_CMD:         /* 关闭定时器 */
            del_timer(&dev->timer);
            break;
        case SET_PERIOD_CMD:     /*  修改定时器的周期  */
            ret = copy_from_user(&value, (int *)arg, sizeof(int));
            if(ret < 0) {
                return -EFAULT;
            }
            spin_lock_irqsave(&dev->lock,flag);
            dev->timerperiod = value;
            spin_unlock_irqrestore(&dev->lock,flag);
            mod_timer(&dev->timer,jiffies + msecs_to_jiffies(value));
            break;
	}
    return 0;
 
}
 
 
 
/* 设备文件操作集合 */
const struct file_operations timer_opts = {
    .owner = THIS_MODULE,
    .open = timer_open,
    .unlocked_ioctl = timer_unlocked_ioctlioctl,
};
 
/* LED灯初始化 */
int led_init(timer_dev * ptimer)
{
    int ret = 0;
    
    timer_dev *dev = ptimer;
 
    /* 获取LED节点和信息 */
    dev->nd = of_find_node_by_path("/gpioled");
 
    /* 得到GPIO的编号 */
    dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpio", 0);
    if(dev->led_gpio < 0) {
        ret = -EINVAL;
        printk("fail get gpio\r\n");
        goto fail_getgpio;
    }
 
    /* 申请GPIO */
    ret = gpio_request(dev->led_gpio, "led_gpio");
    if(ret) {
        printk("fail gpio request\r\n");
        ret = -EBUSY;
        goto fail_request;
    }
 
    /* 设置GPIO输入输出 */
    ret = gpio_direction_output(dev->led_gpio, 1);           /* 输出模式 给1关灯,默认关灯 */
    if(ret){
        printk("fail gpio set output\r\n");
        ret = -EBUSY;
        goto fail_setout;
    }
 
    return 0;
fail_setout:
    gpio_free(dev->led_gpio);
fail_request:
fail_getgpio:
    return ret;
}
 
/* 定时器定时时间到回调函数 */
void  timer_timerout (unsigned long arg)
{
    static int status = 1;
    unsigned long flags;
    int timerperiod  = 0;
    timer_dev *dev = (timer_dev *)arg;
 
    /* 设置LED灯电平 */
    status = !status;
    gpio_set_value(dev->led_gpio,status);
 
    spin_lock_irqsave(&dev->lock,flags);                    /* 自锁 */
    timerperiod = dev->timerperiod;
    spin_unlock_irqrestore(&dev->lock, flags);               /* 解锁 */
 
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));
}
 
 
/* 入口函数 */
static int __init timer_init(void)
{
    int ret = 0;
 
    /* 初始化自旋锁 */
    spin_lock_init(&timer.lock);
    timer.timerperiod = 500;
 
    /* 注册设备号 */
    timer.major = 0;
    if(timer.major) {       /* 指定设备号 */
        timer.devid = MKDEV(timer.major,0);
        ret = register_chrdev_region(timer.devid, TIMER_CNT, TIMER_NAME);
    }else {                 /* 没有指定设备号 */
        ret = alloc_chrdev_region(&timer.devid, 0, TIMER_CNT, TIMER_NAME);
        timer.major = MAJOR(timer.devid);
        timer.minor = MINOR(timer.devid);
    }
    if(ret < 0) {
        printk("fail devid\r\n");
        goto fail_devid;
    }
    printk("major = %d,minor = %d\r\n",timer.major,timer.minor);            /* 打印设备号 */
 
    /* 注册设备 */
    timer.dev.owner = THIS_MODULE;
    cdev_init(&timer.dev, &timer_opts);
    ret = cdev_add(&timer.dev, timer.devid, TIMER_CNT);
    if(ret < 0) {
        printk("fail dev\r\n");
        goto fail_dev;
    }
 
    /* 自动创建节点信息 */
    timer.class = class_create(THIS_MODULE, TIMER_NAME);
    if(IS_ERR(timer.class)) {
        ret = PTR_ERR(timer.class);
        printk("fail class\r\n");
        goto fail_class;
    }
    timer.device = device_create(timer.class, NULL, timer.devid, NULL, TIMER_NAME);
    if(IS_ERR(timer.device)) {
        ret = PTR_ERR(timer.device);
        printk("fail device\r\n");
        goto fail_device;
    }
 
    /* 初始化LED灯 */
    ret = led_init(&timer);
    if(ret < 0) {
        printk("fail led init\r\n");
        goto fail_led_init;
    }
 
    /* 初始化定时器 */
    init_timer(&timer.timer);
    timer.timer.data = (unsigned long) &timer;
    timer.timer.function = timer_timerout;
    mod_timer(&timer.timer, jiffies + msecs_to_jiffies(timer.timerperiod));
 
    return 0;
 
fail_led_init:
fail_device:
    class_destroy(timer.class);
fail_class:
    cdev_del(&timer.dev);
fail_dev:
    unregister_chrdev_region(timer.devid, TIMER_CNT);
fail_devid:
    return ret;
}
 
 
/* 出口函数 */
static void __exit timer_exit(void)
{
    /* 关灯 */
    gpio_set_value(timer.led_gpio, 1);
 
    /* 删除定时器 */
    del_timer_sync(&timer.timer);
 
    /* 注销GPIO */
    gpio_free(timer.led_gpio);
 
    /* 删除类的设备 */
    device_destroy(timer.class, timer.devid);
 
    /* 删除类 */
    class_destroy(timer.class);
 
    /* 删除设备 */
    cdev_del(&timer.dev);
 
    /* 删除设备号 */
    unregister_chrdev_region(timer.devid, TIMER_CNT);
    
    printk("timer exit\r\n");
}
 
 
/* 注册入口和出口函数 */
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZhangXueGuo");
上层代码编写

============

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
 
 
 
/* 命令宏 */
#define OPEN_CMD            _IO('E', 1)
#define CLOSE_CMD           _IO('E', 2)
#define SET_PERIOD_CMD      _IOW('E', 3, int)  
 
/*
 *  main主程序 
 *  argc:argv数字个数,一般指传递给函数的参数数量
 *  argv:具体的参数内容,一般都是字符串格式
 *  return:0表示成功
 * 
*/
int main(int argc, char *argv[])
{
    int fd,ret;
    char *FileName;
    int cmd,arg;
    unsigned char str[100];
 
    /* 判断使用命令参数是否正确 */
    if(argc != 2){
        printf("命令使用错误!\r\n");
        ret = -1;
        goto fail_open;
    }
 
    /* 打开程序 */
    FileName = argv[1];
    fd = open(FileName,O_RDWR);
    if(fd < 0){
        printf("应用程序打开设备文件失败!\r\n");
        ret = fd;
        goto fail_open;
    }
 
    
    while(1) {
        printf("Please input CMD:");
        ret = scanf("%d",&cmd);
        if(ret != 1) {
            gets(str);
        }
 
        if(cmd == 1) {
            ret = ioctl(fd, OPEN_CMD, &arg);
        } else if(cmd == 2) {
            ret = ioctl(fd, CLOSE_CMD, &arg);
        } else if(cmd == 3) {
            printf("Input period:");
            ret = scanf("%d",&arg);
            if(ret != 1) { 
                gets(str);
            }
            ret = ioctl(fd, SET_PERIOD_CMD, &arg);
        }
    }
 
    /* 关闭文件 */
    close(fd);
    return 0;
 
fail_open:
    return ret;
}

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

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

相关文章

高阶数据结构与算法——红黑树の奥秘

1.认识红黑树 1.1红黑树的概念 红⿊树是⼀棵⼆叉搜索树&#xff0c;他的每个结点增加⼀个存储位来表⽰结点的颜⾊&#xff0c;可以是红⾊或者⿊⾊。通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束&#xff0c;红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍&#xff0c…

graphrag学习总结

学习视频&#xff1a;b站链接 项目链接 GraphRAG 的基本概念 Document&#xff08;文档&#xff09;&#xff1a;系统中的输入文档。这些文档要么代表CSV中的单独行&#xff0c;要么代表单独的txt文件。 TextUnit&#xff08;文本块&#xff09;&#xff1a;要分析的文本块。…

pdf阅读软件有哪些?5个软件帮助你快速进行pdf阅读

pdf阅读软件有哪些&#xff1f;5个软件帮助你快速进行pdf阅读 如果你正在寻找优秀的PDF阅读软件&#xff0c;以下推荐的5款软件能够帮助你快速、轻松地阅读和管理PDF文件。这些工具各具特色&#xff0c;适用于不同的使用需求&#xff0c;无论是注释、签名、还是管理大文件&…

使用Rollup.js快速开始构建一个前端项目

Rollup 是一个用于 JavaScript 项目的模块打包器&#xff0c;它将小块代码编译成更大、更复杂的代码&#xff0c;例如库或应用程序。Rollup 对代码模块使用 ES6 模块标准&#xff0c;它支持 Tree-shaking&#xff08;摇树优化&#xff09;&#xff0c;可以剔除那些实际上没有被…

lwip多线程使用说明,注意事项

lwIP: Common pitfalls 相关问题&#xff1a; lwIP - A Lightweight TCP/IP stack - Bugs: bug #56531, Missing locking in tcp_input() [Savannah] 我也碰到了差不多情况

goDoH:一款使用了DNS-over-HTTPS作为传输端口的C2框架

工具介绍 goDoH是一款概念验证命令与控制框架&#xff0c;该工具采用Golang开发&#xff0c;并使用了DNS-over-HTTP作为传输介质。 项目依赖 goDoH的代码构建需要使用到Dep&#xff08;【GitHub传送门】&#xff09;&#xff0c;Dep是一款针对Go语言的依赖管理工具。 在mac…

【vue】vue-router_ vue3路由管理器

代码获取 vue-router_ vue3路由管理器 ⼀、基本介绍 1. 单⻚应⽤程序介绍 1.1 概念 单⻚应⽤程序&#xff1a;SPA(Single Page Application)是指所有的功能都在⼀个HTML⻚⾯上实现 1.2 具体⽰例 单⻚应⽤⽹站&#xff1a; ⽹易云⾳乐 https://music.163.com/ 多⻚应⽤⽹…

【Python语言进阶(二)】

一、函数的使用方式 将函数视为“一等公民” 函数可以赋值给变量函数可以作为函数的参数函数可以作为函数的返回值 高阶函数的用法&#xff08;filter、map以及它们的替代品&#xff09; items1 list(map(lambda x: x ** 2, filter(lambda x: x % 2, range(1, 10)))) # filter…

K8s简介和安装部署

一、 Kubernetes 简介及部署方法 1、应用部署方式演变 Kubernetes简称为K8s&#xff0c;是用于自动部署、扩缩和管理容器化应用程序的开源系统&#xff0c;起源于Google 集群管理工具Borg。 传统部署 &#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优…

python yolov8半自动标注

首先标注一部分图片&#xff0c;进行训练&#xff0c;生成模型&#xff0c;标注文件为xml方便后面统一做处理。 1、标注数据&#xff08;文件为xml, 转为txt用于训练&#xff0c;保留xml标签文件&#xff09; 2、模型训练&#xff08;训练配置、训练代码、&#xff09; 3、使用…

400行程序写一个实时操作系统(八):(必看!)使用gdb调试错误的程序

上一篇笔者讲完了内存管理算法的完整实现&#xff0c;不过差点忘了&#xff0c;直接上这一部分是不是有点不友好&#xff0c;要知道笔者当初写内存算法可是调试得死去活来&#xff0c;奇奇怪怪的问题不断出现。 就比如笔者当初写了一个内存池算法&#xff0c;结果奇葩的事情发…

大模型之三十二-语音合成TTS(coqui) 之二 fine-tune

在 大模型之三十-语音合成TTS(coqui)[shichaog CSDN]中提到了xttsv2的fine-tune。 数据情况&#xff1a; 我是从bilibili up主小Lin说提取了一些视频&#xff0c;然后进行了重新的fine-tune。 训练结果 如下图所示&#xff0c;上面波形幅度较大的是xttsv2原始模型的结果&am…

u盘被写保护怎么解除?u盘写保护怎么去掉?

我们平时在使用U盘的过程中&#xff0c;可能会遇到U盘无法写入文件、删除数据或是格式化的情况。同时还可能收到提示“改磁盘已写保护”。U盘被写保护可能是有意的设置&#xff0c;也可能是无疑的操作。那么U盘被写保护了该怎么解除呢&#xff1f;本期内容&#xff0c;我们将介…

最新版 Winows下如何安装Redis?

最新版 Winows下如何安装Redis&#xff1f; 一、Redis介绍 Redis是一个广泛使用的开源非关系型数据库&#xff0c;它在现代软件开发中扮演着重要角色。**作为一个基于内存的数据库&#xff0c;Redis的底层代码是用ANSI C编写的&#xff0c;这使得它在性能上非常出色。**Redis…

【AIF-C01认证】亚马逊云科技生成式 AI 认证正式上线啦

文章目录 一、AIF-C01简介二、考试概览三、考试知识点3.1 AI 和 ML 基础知识3.2 生成式人工智能基础3.3 基础模型的应用3.4 负责任 AI 准则3.5 AI 解决方案的安全性、合规性和监管 四、备考课程4.1 「备考训练营」 在线直播课4.2 「SkillBuilder」学习课程 五、常见问题六、参考…

前端开发攻略---使用ocr识别图片进行文字提取功能

1、引入资源 通过链接引用 <script src"https://cdn.bootcdn.net/ajax/libs/tesseract.js/5.1.0/tesseract.min.js"></script> npm或其他方式下载 npm i tesseract 2、示例 <!DOCTYPE html> <html lang"en"><head><meta…

从纸质到云端:3C产品说明书的电子化进程与影响

在科技日新月异的今天&#xff0c;3C产品&#xff08;计算机类、通信类和消费类电子产品&#xff09;作为现代生活不可或缺的一部分&#xff0c;其说明书的演变也见证了技术进步的足迹。从最初的纸质文档到如今的电子说明书&#xff0c;这一转变不仅仅是物理形态的转换&#xf…

UE5 圆周运动、贝塞尔曲线运动、贝塞尔曲线点

圆周运动 贝塞尔曲线路径运动 蓝图函数库创建贝塞尔曲线点 // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "MyBlu…

文件IO知识梳理及练习

1> 使用fread和fwrite完成两个文件的拷贝&#xff0c;要求源文件和目标文件由外界输入 #include <myhead.h> typedef struct sockaddr_in addr_in_t; typedef struct sockaddr addr_t; typedef struct sockaddr_un addr_un_t; int main(int argc, const char *argv[])…

使用OpenCV实现基于FisherFaces的人脸识别

引言 随着人工智能技术的发展&#xff0c;人脸识别已经成为日常生活中不可或缺的一部分。在众多的人脸识别算法中&#xff0c;FisherFaces 方法因其简单易用且具有良好的识别效果而备受青睐。本文将详细介绍如何使用Python和OpenCV库实现基于FisherFaces的人脸识别系统&#x…