Linux驱动编程(驱动程序基石)(上)

news2025/1/23 9:08:51

一、休眠与唤醒

在这里插入图片描述
要休眠的线程,放在 wq 队列里,中断处理函数从 wq 队列里把它取出来唤醒。所以,我们要做这几件事:
① 初始化 wq 队列
② 在驱动的 read 函数中,调用 wait_event_interruptible:
它本身会判断 event 是否为 FALSE,如果为 FASLE 表示无数据,则休眠。
当从 wait_event_interruptible 返回后,把数据复制回用户空间。
③ 在中断服务程序里:
设置 event 为 TRUE,并调用 wake_up_interruptible 唤醒线程。

1、驱动关键代码
初始化等待队列

 static DECLARE_WAIT_QUEUE_HEAD(gpio_key_wait);

...

//在驱动的读函数里调用 wait_event_interruptible
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;

	wait_event_interruptible(gpio_key_wait, g_key);  //不一定会进入休眠,它会先判断 g_key 是否为 TRUE
	err = copy_to_user(buf, &g_key, 4);
	g_key = 0;
	
	return 4;
}

...

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	int val;
	val = gpiod_get_value(gpio_key->gpiod);
	
	printk("key %d %d\n", gpio_key->gpio, val);
	g_key = (gpio_key->gpio << 8) | val;  	//确定按键值 g_key,g_key 也就变为 TRUE 了
	wake_up_interruptible(&gpio_key_wait);	//唤醒 gpio_key_wait 中的第 1 个线程
	
	return IRQ_HANDLED;
}

注意这 2 个函数,一个没有使用“&”,另一个使用了“&”:

wait_event_interruptible(gpio_key_wait, g_key);
wake_up_interruptible(&gpio_key_wait);

2、应用程序关键代码

/* 2. 打开文件 */
fd = open(argv[1], O_RDWR);

if (fd == -1)
{
	printf("can not open file %s\n", argv[1]);
	return -1;
}

while (1)   //循环读,app基本处于休眠
{
	/* 3. 读文件 */
	read(fd, &val, 4);
	printf("get button : 0x%x\n", val);
}

3、使用环形缓冲区改进驱动程序

在这里插入图片描述
使用环形缓冲区之后,休眠函数可以这样写:

wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());
key = get_key();
err = copy_to_user(buf, &key, 4);

唤醒函数可以这样写:

key = (gpio_key->gpio << 8) | val;
put_key(key);
wake_up_interruptible(&gpio_key_wait);

二、poll机制

在这里插入图片描述
函数执行流程如上图①~⑧所示,重点从③开始看。假设一开始无按键数据:
③ APP 调用 poll 之后,进入内核态;
④ 导致驱动程序的 drv_poll 被调用:
注意,drv_poll 要把自己这个线程挂入等待队列 wq 中;假设不放入队列里,那以后发生中断时,中断服务程序去哪里找到你嘛?
⑤ 假设当前没有数据,则休眠一会;
⑥ 在休眠过程中,按下了按键,发生了中断:
在中断服务程序里记录了按键值,并且从 wq 中把线程唤醒了。
⑦ 线程从休眠中被唤醒,继续执行 for 循环,再次调用 drv_poll:drv_poll 返回数据状态
⑧ 哦,你有数据,那从内核态返回到应用态吧
⑨ APP 调用 read 函数读数据
如果一直没有数据,调用流程也是类似的,重点从③开始看,如下:
③ APP 调用 poll 之后,进入内核态;
④ 导致驱动程序的 drv_poll 被调用:
⑤ 假设当前没有数据,则休眠一会;
⑥ 在休眠过程中,一直没有按下了按键,超时时间到:内核把这个线程唤醒;
⑦ 线程从休眠中被唤醒,继续执行 for 循环,再次调用 drv_poll:drv_poll 返回数据状态
⑧ 哦,你还是没有数据,但是超时时间到了,那从内核态返回到应用态吧
⑨ APP 不能调用 read 函数读数据
注意几点
① drv_poll 要把线程挂入队列 wq,但是并不是在 drv_poll 中进入休眠,而是在调用 drv_poll 之后休眠
② drv_poll 要返回数据状态
③ APP 调用一次 poll,有可能会导致 drv_poll 被调用 2 次
④ 线程被唤醒的原因有 2:中断发生了去队列 wq 中把它唤醒,超时时间到了内核把它唤醒
⑤ APP 要判断 poll 返回的原因:有数据,还是超时。有数据时再去调用 read 函数。

2.1、驱动编程
使用 poll 机制时,驱动程序的核心就是提供对应的 drv_poll 函数。
在 drv_poll 函数中要做 2 件事:
① 把当前线程挂入队列 wq:poll_wait
APP 调用一次 poll,可能导致 drv_poll 被调用 2 次,但是我们并不需要把当前线程挂入队列 2 次。
可以使用内核的函数 poll_wait 把线程挂入队列,如果线程已经在队列里了,它就不会再次挂入。
② 返回设备状态:
APP 调用 poll 函数时,有可能是查询“有没有数据可以读”:POLLIN,也有可能是查询“你有没有空间给我写数据”:POLLOUT。
所以 drv_poll 要返回自己的当前状态:(POLLIN | POLLRDNORM)(POLLOUT | POLLWRNORM)
POLLRDNORM等同于 POLLIN,为了兼容某些 APP 把它们一起返回。
POLLWRNORM等同于 POLLOUT ,为了兼容某些 APP 把它们一起返回。

APP 调用 poll 后,很有可能会休眠。对应的,在按键驱动的中断服务程序中,也要有唤醒操作。
驱动程序中 poll 的代码如下:

static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_key_wait, wait);
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

在这里插入图片描述
在调用 poll 函数时,要指明:
① 你要监测哪一个文件:哪一个 fd
② 你想监测这个文件的哪种事件:是 POLLIN、还是 POLLOUT
最后,在 poll 函数返回时,要判断状态。

应用程序代码如下:

struct pollfd fds[1];
int timeout_ms = 5000;
int ret;

fds[0].fd = fd;
fds[0].events = POLLIN;		//想等待什么事件

ret = poll(fds, 1, timeout_ms);
if ((ret == 1) && (fds[0].revents & POLLIN))	//得到了什么事件
{
	read(fd, &val, 4);
	printf("get button : 0x%x\n", val);
}

三、异步通知

使用休眠-唤醒、POLL 机制时,都需要休眠等待某个事件发生时,它们的差别在于后者可以指定休眠的时长。

Linux下的信号
在这里插入图片描述
给某个信号注册处理函数,用法如下:
在这里插入图片描述
APP 还要做什么事?想想这几个问题:
① 内核里有那么多驱动,你想让哪一个驱动给你发 SIGIO 信号?
APP 要打开驱动程序的设备节点。
② 驱动程序怎么知道要发信号给你而不是别人?
APP 要把自己的进程 ID 告诉驱动程序。
③ APP 有时候想收到信号,有时候又不想收到信号:
应该可以把 APP 的意愿告诉驱动。

驱动程序要做什么?发信号。
① APP 设置进程 ID 时,驱动程序要记录下进程 ID;
② APP 还要使能驱动程序的异步通知功能,驱动中有对应的函数:
APP 打开驱动程序时,内核会创建对应的 file 结构体,file 中有 f_flags;
f_flags 中有一个 FASYNC 位,它被设置为 1 时表示使能异步通知功能。
当 f_flags 中的 FASYNC 位发生变化时,驱动程序的 fasync 函数被调用。
③ 发生中断时,有数据时,驱动程序调用内核辅助函数发信号。
这个辅助函数名为 kill_fasync。


重点从②开始:
② APP 给 SIGIO 这个信号注册信号处理函数 func,以后 APP 收到 SIGIO 信号时,这个函数会被自动调用;
③ 把 APP 的 PID(进程 ID)告诉驱动程序,这个调用不涉及驱动程序,在内核的文件系统层次记录 PID;
④ 读取驱动程序文件 Flag;
⑤ 设置 Flag 里面的 FASYNC 位为 1:当 FASYNC 位发生变化时,会导致驱动程序的 fasync 被调用;
⑥⑦ 调用 faync_helper,它会根据 FAYSNC 的值决定是否设置 button_async->fa_file=驱动文件 filp:驱动文件 filp 结构体里面含有之前设置的 PID。
⑧ APP 可以做其他事;
⑨⑩ 按下按键,发生中断,驱动程序的中断服务程序被调用,里面调用 kill_fasync 发信号;
⑪⑫⑬ APP 收到信号后,它的信号处理函数被自动调用,可以在里面调用 read 函数读取按键。

驱动编程
① 提供对应的 drv_fasync 函数;
② 并在合适的时机发信号。

drv_fasync 函数很简单,调用 fasync_helper 函数就可以,如下:

static struct fasync_struct *button_async;
static int drv_fasync (int fd, struct file *filp, int on)
{
	return fasync_helper (fd, filp, on, &button_async);
}

fasync_helper 函数会分配、构造一个 fasync_struct 结构体 button_async:
① 驱动文件的 flag 被设置为 FAYNC 时:

button_async->fa_file = filp; // filp 表示驱动程序文件,里面含有之前设置的 PID

② 驱动文件被设置为非 FASYNC 时:

button_async->fa_file = NULL;

以后想发送信号时,使用 button_async 作为参数就可以,它里面“可能”含有 PID。

怎么发信号呢?代码如下:

kill_fasync (&button_async, SIGIO, POLL_IN);

第 1 个参数:button_async->fa_file 非空时,可以从中得到 PID,表示发给哪一个 APP;
第 2 个参数表示发什么信号:SIGIO;
第 3 个参数表示为什么发信号:POLL_IN,有数据可以读了。(APP 用不到这个参数)

应用编程

① 编写信号处理函数:

static void sig_func(int sig)
{
	int val;
	read(fd, &val, 4);
	printf("get button : 0x%x\n", val);
}

② 注册信号处理函数:

signal(SIGIO, sig_func);

③ 打开驱动:

fd = open(argv[1], O_RDWR);

④ 把进程 ID 告诉驱动:

fcntl(fd, F_SETOWN, getpid());

⑤ 使能驱动的 FASYNC 功能:

flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);

四、阻塞与非阻塞

所谓阻塞,就是等待某件事情发生。比如调用 read 读取按键时,如果没有按键数据则 read 函数不会返回,它会让线程休眠等待。
使用 poll 时,如果传入的超时时间不为 0,这种访问方法也是阻塞的。

注意:对于普通文件、块设备文件,O_NONBLOCK 不起作用。
注意:对于字符设备文件,O_NONBLOCK 起作用的前提是驱动程序针对 O_NONBLOCK 做了处理。

只能在 open 时表明 O_NONBLOCK 吗?在 open 之后,也可以通过 fcntl 修改为阻塞或非阻塞。

应用编程
open 时设置:

int fd = open(/dev/xxx”, O_RDWR | O_NONBLOCK); /* 非阻塞方式 */
int fd = open(/dev/xxx”, O_RDWR ); /* 阻塞方式 */

open 之后设置:

int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK); /* 非阻塞方式 */
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); /* 阻塞方式 */

驱动编程

static ssize_t drv_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
		return -EAGAIN;
	wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
		 ……
}

驱动开发原则

驱动程序程序“只提供功能,不提供策略”。就是说驱动程序可以提供休眠唤醒、查询等等各种方式,驱动程序只提供这些能力,怎么用由 APP 决定。

五、定时器

5.1、内核函数

在内核中使用定时器很简单,涉及这些函数(参考内核源码 include\linux\timer.h):
① setup_timer(timer, fn, data):
设置定时器,主要是初始化 timer_list 结构体,设置其中的函数、参数。
② void add_timer(struct timer_list *timer):
向内核添加定时器。timer->expires 表示超时时间。
当超时时间到达,内核就会调用这个函数:timer->function(timer->data)。
③ int mod_timer(struct timer_list *timer, unsigned long expires):
修改定时器的超时时间,
它等同于:del_timer(timer); timer->expires = expires; add_timer(timer);
但是更加高效。
④ int del_timer(struct timer_list *timer):
删除定时器。

5.2、定时器时间单位

定时器的时间就是基于 jiffies 的,我们修改超时时间时,一般使用这 2 种方法:
① 在 add_timer 之前,直接修改:

timer.expires = jiffies + xxx; // xxx 表示多少个滴答后超时,也就是 xxx*10ms
timer.expires = jiffies + 2*HZ; // HZ 等于 CONFIG_HZ,2*HZ 就相当于 2 秒

② 在 add_timer 之后,使用 mod_timer 修改:

mod_timer(&timer, jiffies + xxx); // xxx 表示多少个滴答后超时,也就是 xxx*10ms
mod_timer(&timer, jiffies + 2*HZ); // HZ 等于 CONFIG_HZ,2*HZ 就相当于 2 秒

5.3、使用定时器处理按键抖动
在这里插入图片描述按下或松开一个按键,它的 GPIO 电平会反复变化,最后才稳定。一般是几十毫秒才会稳定。如果不处理抖动的话,用户只操作一次按键,中断程序可能会上报多个数据。怎么处理?
① 在按键中断程序中,可以循环判断几十亳秒,发现电平稳定之后再上报
② 使用定时器
显然第 1 种方法太耗时,违背“中断要尽快处理”的原则,你的系统会很卡。
怎么使用定时器?看下图:
在这里插入图片描述核心在于:在 GPIO 中断中并不立刻记录按键值,而是修改定时器超时时间,10ms 后再处理。
如果 10ms 内又发生了 GPIO 中断,那就认为是抖动,这时再次修改超时时间为 10ms。
只有 10ms 之内再无 GPIO 中断发生,那么定时器的函数才会被调用。
在定时器函数中记录按键值。

六、中断下半部tasklet

在前面我们介绍过中断上半部、下半部。中断的处理有几个原则:
① 不能嵌套;
② 越快越好。
在处理当前中断时,即使发生了其他中断,其他中断也不会得到处理,所以中断的处理要越快越好。但是某些中断要做的事情稍微耗时,这时可以把中断拆分为上半部、下半部。
在上半部处理紧急的事情,在上半部的处理过程中,中断是被禁止的;
在下半部处理耗时的事情,在下半部的处理过程中,中断是使能的。

6.1、内核函数
中断下半部使用结构体 tasklet_struct 来表示,它在内核源码 include\linux\interrupt.h 中定义:

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

其中的 state 有 2 位:

① bit0 表示 TASKLET_STATE_SCHED
等于 1 时表示已经执行了 tasklet_schedule 把该 tasklet 放入队列了;tasklet_schedule 会判断该位,如果已经等于 1 那么它就不会再次把 tasklet 放入队列。
② bit1 表示 TASKLET_STATE_RUN
等于 1 时,表示正在运行 tasklet 中的 func 函数;函数执行完后内核会把该位清 0。

其中的 count 表示该 tasklet 是否使能:等于 0 表示使能了,非 0 表示被禁止了。对于 count 非 0 的tasklet,里面的 func 函数不会被执行。

使用中断下半部之前,要先实现一个 tasklet_struct 结构体,这可以用这 2 个宏来定义结构体:

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

使用 DECLARE_TASKLET 定义的 tasklet 结构体,它是使能的;
使 用 DECLARE_TASKLET_DISABLED 定义 的 tasklet 结构体,它是禁止的;使用之前要先调用tasklet_enable 使能它。

也可以使用函数来初始化 tasklet 结构体:

extern void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);

使能/禁止 tasklet

static inline void tasklet_enable(struct tasklet_struct *t);
static inline void tasklet_disable(struct tasklet_struct *t);

调度 tasklet

static inline void tasklet_schedule(struct tasklet_struct *t);

kill tasklet

extern void tasklet_kill(struct tasklet_struct *t);

6.2、tasklet 使用方法

先定义 tasklet,需要使用时调用 tasklet_schedule,驱动卸载前调用 tasklet_kill。
tasklet_schedule 只是把 tasklet 放入内核队列,它的 func 函数会在软件中断的执行过程中被调用。

七、工作队列

前面讲的定时器、下半部 tasklet,它们都是在中断上下文中执行,它们无法休眠。当要处理更复杂的事情时,往往更耗时。这些更耗时的工作放在定时器或是下半部中,会使得系统很卡;并且循环等待某件事情完成也太浪费 CPU 资源了。

如果使用线程来处理这些耗时的工作,那就可以解决系统卡顿的问题:因为线程可以休眠。

工作队列的应用场合:要做的事情比较耗时,甚至可能需要休眠,那么可以使用工作队列。
缺点:多个工作(函数)是在某个内核线程中依序执行的,前面函数执行很慢,就会影响到后面的函数。
在多 CPU 的系统下,一个工作队列可以有多个内核线程,可以在一定程度上缓解这个问题。

7.1、内核函数

内核线程、工作队列(workqueue)都由内核创建了,我们只是使用。使用的核心是一个 work_struct 结构体,定义如下:
在这里插入图片描述
使用工作队列时,步骤如下:
① 构造一个 work_struct 结构体,里面有函数;
② 把这个 work_struct 结构体放入工作队列,内核线程就会运行 work 中的函数。

定义 work

参考内核头文件:include\linux\workqueue.h

#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
#define DECLARE_DELAYED_WORK(n, f) \
struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)

如果要在代码中初始化 work_struct 结构体,可以使用下面的宏:

#define INIT_WORK(_work, _func)

使用 work:schedule_work

调用 schedule_work 时,就会把 work_struct 结构体放入队列中,并唤醒对应的内核线程。内核线程就会从队列里把 work_struct 结构体取出来,执行里面的函数。

其他函数
在这里插入图片描述

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

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

相关文章

English Learning - L3 作业打卡 Lesson1 Day7 2023.5.11 周四

English Learning - L3 作业打卡 Lesson1 Day7 2023.5.11 周四 引言&#x1f349;句1: Blues is slow, sad and soulful.成分划分弱读连读爆破语调 &#x1f349;句2: Duke Ellington and his orchestra recorded a famous song – Mood Indigo – about the deep blue color, …

【嵌入式环境下linux内核及驱动学习笔记-(11-设备树)】

目录 1、设备树体系1.1 DTS /DTSI / DTC / DTB 2、基础语法2.1 节点语法2.1.1 通用名称建议 2.2 属性语法2.2.1 属性值 2.3 关于label2.4 节点的[unit-address] 与reg属性2.5 根节点 /2.6 标准属性compatible2.6.1 of_machine_is_compatible函数 2.7 地址编码2.7.1 标准属性#ad…

Linux常用命令(4)

文章目录 Linux常用命令(4)查找文件或目录 find在当前文件下找一个hello.txt的文件在/user/include目录下找stdio.h文件 查找内容 grep在当前目录的hello.txt 文件中搜索1234在当前目录下所有文件中搜索1234在当前目录下所有以.txt结尾的文件中搜索1234 制作压缩包和解压缩命令…

Java 函数式编程 详细介绍

在兼顾面向对象特性的基础上&#xff0c;Java语言通过Lambda表达式与方法引用等&#xff0c;为开发者打开了函数式编程的大门。 下面我们做一个初探。 Lambda的延迟执行 有些场景的代码执行后&#xff0c;结果不一定会被使用&#xff0c;从而造成性能浪费。而Lambda表达式是延…

PID现场参数调试解密

看图 反馈与给定曲线 1.超调过大&#xff0c;减小比例&#xff0c;增大积分时间 2.迅速变化&#xff0c;存在小超调 3.实际值缓慢接近设定值&#xff0c;并且无超调的达到设定值 4.增益系数太小和/或微分时间太长 5.益系数太小和/或积分时间太长 公式(西门子FB41) 举例 现象…

20230513查找瑞芯微RK3588开发板以及对DP接口的支持

20230513查找瑞芯微RK3588开发板以及对DP接口的支持 2023/5/13 17:43 01、t-firefly https://www.t-firefly.com/ https://www.t-firefly.com/product/industry/aio1684xjd4 https://www.t-firefly.com/product/industry/aio3588q https://item.taobao.com/item.htm?spma1z10.…

三轴和直剪

目录 常规三轴 成样 预压 围压加载 二维直剪 成样 预压 围压加载 常规三轴 成样 cylinder keyword ...(3D ONLY) Generate a cylinder in 3D. If the name keyword has not been specified, then s cylinderWall. By default, the cylinder is closed and each side o…

HTTP协议(二)/HTTPS

HTTPS是啥 https是在http协议的基础上&#xff0c;加上了数据的加密解密层&#xff0c;即TLS/SSL。在进行http请求的时候&#xff0c;通过TLS/SSL进行加密&#xff0c;在响应的时候&#xff0c;也会通过TLS/SSL进行解密。加密解密层TLS/SSL不会对下三层加密解密&#xff0c;而…

【27】核心易中期刊推荐——计算机工程与技术

🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…

[ 云计算 华为云 ] 华为云开天 aPaaS:构建高效的企业数字化平台(下)

文章目录 前言四、华为云开天aPaaS 核心功能4.1 业务模型管理4.2 连接器4.2.1 连接器的种类4.2.1.1 公共连接器4.2.1.2 私有连接器 4.2.2连接器的开发步骤 4.3 自动化流4.3.1 自动化流介绍4.3.2 自动化流日志监控 4.4 自定义逻辑处理4.5 分享连接器和流模板 五、aPaaS 的应用实…

关于C/C++语言重复包含头文件,编译时报错已定义的宏未定义的原因及解决方法

在编写一个文件较多的单片机程序时&#xff0c;为了在一个文件中定义的变量或宏能被另一个文件使用&#xff0c;经常会写成在多个头文件相互包含&#xff0c;由此将可能会导致明明已经定义的宏&#xff0c;且已经将宏所在的文件使用 #include 包含&#xff0c;编译时仍会报错未…

自学大语言模型的应用程序框架Langchain(初入门)

现阶段chatGPT非常火热。带动了第三方开源库&#xff1a;LangChain火热。它是一个在大语言模型基础上实现联网搜索并给出回答、总结 PDF 文档、基于某个 Youtube 视频进行问答等等的功能的应用程序。 什么是Langchain LangChain 是一个用于开发由语言模型驱动的应用程序的框架…

Vue 3.3 浪客剑心

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 特征弃用一、 < s c r i p t s e t u p > <script setup…

【啃书C++Primer5】-编写一个简单C++程序

每个C程序都包含一个或多个函数(function)&#xff0c;其中一个必须命名为main。操作系统通过调用main来运行C程序。下面是一个非常简单的main函数&#xff0c;它什么也不干&#xff0c;只是返回给操作系统一个值: int main() {return 0; }一个函数的定义包含四部分:返回类型(r…

【讨论话题】 工作中应不应该出于好心,帮助同事完成不属于你职责范围内的问题?

前言 职场如战场&#xff0c;职场中经常有一种好心帮忙反而成了义务的乱相存在。我们要做一个好人&#xff0c;同事要防备一些坏人。 勿以善小而不为&#xff0c;勿以恶小而为之 文章目录 前言社群内部讨论社群内部讨论总结师父的回答如何优雅的说不总结 社群内部讨论 先来看…

声音合成——Foley Sound——DECASE项目——多模态智能感知与应用——代码实现(6)

文章目录 概述encoder的编写过程代码编写运行结果问题总结 decoder的编写过程知识补充关于逆卷积 代码编写运行结果总结 Autoencoder模型编写 compile方法 train方法 保存和加载模型模块编写实现代码——autoencoder代码实现代码——train代码实现代码——保存和加载模型的代…

GPT-4最强竞品迎来重磅升级:一次10万token,百页资料一分钟总结完毕

夕小瑶科技说 分享 来源 | 量子位 作者 | 丰色 萧箫 号称ChatGPT“最强竞争对手”的Claude&#xff0c;今天迎来史诗级更新—— 模型记忆力原地起飞&#xff0c;现在1分钟看完一本数万字的小说&#xff0c;已经不在话下。 消息一出&#xff0c;评论区直接炸了&#xff0c;网友…

HTTP第九讲——你能写出正确的网址吗?

应该用什么来标记服务器上的资源呢&#xff1f;怎么区分“这个”资源和“那个”资源呢&#xff1f; 用的是 URI&#xff0c;也就是统一资源标识符&#xff08;Uniform Resource Identifier&#xff09;。因为它经常出现在浏览器的地址栏里&#xff0c;所以俗称为“网络地址”&a…

猫妹人生中的第一张Python证书长这样(1)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 欢迎和猫妹一起&#xff0c;快乐学习&#xff0c;茁壮成长&#xff01; 猫妹和她的朋友们 猫妹和她的朋友们有两层含义。 一是新的合集名称&#xff0c;在这个合集里面&am…

redis从零开始(3)----基本类型hyperloglog/geo/stream

接上文 redis基本类型 HyperLogLog 简介 HyperLogLog是用于「统计基数」的数据集合类型&#xff0c;基数统计就是指统计一个集合中不重复的元素个数&#xff0c; 但是准确率不是百分百&#xff0c;即他可以提供不精确的去重计数。HyperLogLog 的优点是&#xff0c;在输入元素…