《操作系统导论》笔记

操作系统三个关键:虚拟化( virtualization) 并发(concurrency) 持久性(persistence)

1 CPU虚拟化

1.1 进程

虚拟化CPU:许多任务共享物理CPU,让它们看起来像是同时运行。

时分共享:运行一个进程一段时间,然后运行另一个进程,如此轮换,以此实现虚拟化。

进程创建:将代码和所有静态数据加载到内存中,分配栈内存并初始化,初始化I/O等模块,执行main()函数

进程三种状态:运行、就绪、阻塞
在这里插入图片描述
数据结构:操作系统会保存进程相关的信息,比如进程列表,寄存器上下文等。

进程API:

  • fork():进程分裂出一个子进程,会获得父进程的内存、句柄等资源的副本。
  • wait():等待任意一个子进程执行完毕
  • exec():将当前进程替换为一个新程序

这种fork()及exec()的方式有利于编写shell脚本。

1.2 机制:受限直接执行

虚拟化CPU两个关键问题:高效、可控

受限直接执行:程序直接在CPU上运行,但指令执行和执行时间受到限制。
用户程序运行于用户模式,执行I/O等操作是受限的;操作系统运行于内核模式,允许执行受限操作。
程序执行受限操作时,需要执行系统调用,此时会陷入内核模式,操作系统执行完操作后,再从陷阱返回到用户模式。陷入内核会保存进程的寄存器以及程序计数器,从陷阱返回时恢复。
系统启动时会初始化陷阱表,告诉CPU陷阱指令对应的处理程序地址。
在这里插入图片描述
进程切换:程序是直接运行在CPU上的,此时操作系统并没有运行,操作系统需要重新获取CPU控制权,才能进行进程切换。获取控制权有两种方式:

  • 协作方式
    进程执行系统调用、或执行一些非法操作时,会将CPU的控制权转移给操作系统,若程序进入死循环,则无法切换
  • 非协作方式
    通过时钟中断,中断同样是陷阱指令,时钟设备可以每几毫秒产生一次中断,中断时会执行操作系统的中断处理程序

进程切换时系统会执行上下文切换:把当前程序寄存器保存到它的内核栈,然后从即将执行的程序的内核栈恢复寄存器。
在这里插入图片描述

1.3 进程调度

指标:

  • 任务周转时间:任务完成时间减去任务到达系统的时间。
  • 响应时间:从任务到达系统到首次运行的时间
  • 公平

非抢占式调度:一个任务完成,再执行下一个任务
抢占式调度:可以中断一个任务,执行另一个任务

先进先出(FIFO)
先到的任务先执行,非抢占式。若短时间任务排在长时间任务之后,会导致平均周转时间很长。

最短任务优先(SJF)
先运行时间最短的任务,非抢占式。平均周转时间优于FIFO。

最短完成时间优先(STCF)
每当新任务进入时,执行剩余时间最少的任务,抢占式。平均周转时间优于SJF。

以上调度算法适用于批处理系统,响应时间和交互性很差。

轮转(RR)
一个工作运行一个时间片,然后切换到下一个任务,依次循环。抢占式。
时间片越短响应性越好,但上下文切换的成本影响整体性能。
任务在I/O期间不会使用CPU,此时可以切换到下一个任务。

调度:多级反馈队列(MLFQ)

目的:在运行过程中学习进程的特征,做出更好的调度决策,以此优化周转时间和响应时间。

一种具体算法:为每个工作设置一个优先级,优先级会动态改变,满足以下规则

  1. 如果A的优先级 > B的优先级,运行A。
  2. 如果A的优先级 = B的优先级,轮转运行A和B。
  3. 工作进入系统时,放在最高优先级。
  4. 一旦工作用完了其在某一层中的时间配额(无论中间主动放弃了多少次CPU),就降低其优先级。
  5. 经过一段时间S,就将系统中所有工作重新加入最高优先级队列。

调度:比例份额

目的:确保每个工作获得一定比例的CPU时间

一种具体实现是彩票调度,每个进程获得一定数量的彩票,每次调度时随机抽取彩票,以此决定调度哪个进程。难点在于如何分配彩票,常用于虚拟机等容易确定额度的系统中。

1.4 多处理器调度

多处理器与单处理器的区别在于对硬件缓存的使用,以及多处理器之间共享数据的方式。
在这里插入图片描述
缓存是基于局部性的概念:

  • 时间局部性:指当一个数据被访问后,它很有可能会在不久的将来被再次访问,比如循环代码中的数据或指令本身。
  • 空间局部性:指当程序访问地址为x的数据时,很有可能会紧接着访问x周围的数据,比如遍历数组或指令的顺序执行。

缓存一致性:
多个CPU同时访问内存会出现缓存一致性问题,即一个CPU在本地缓存中修改了数据,新的值不会立即同步到内存,此时另一个CPU读取内存的值,就会出现两个CPU看到的值不一致。硬件提供了基本解决方案:通过监控内存访问,硬件可以保证获得正确的数据,并保证共享内存的唯一性,比如总线窥探技术。
但是跨CPU访问仍需要使用互斥原语或无锁数据结构来保证正确性。比如多CPU修改同一个队列。

总线窥探:每个缓存都通过监听链接所有缓存和内存的总线,来发现内存访问。如果CPU发现对它放在缓存中的数据的更新,会作废本地副本,或更新它。

缓存亲和度
多处理器调度还有缓存亲和度的问题:一个进程在某个CPU上运行时,会在该CPU的缓存中维护许多状态。下次该进程在相同CPU上运行时,由于缓存中的数据而执行得更快。

单队列多处理器调度(SQMS)
与单处理器调度类似,将所有工作放入一个单独的队列中,问题在于多CPU访问队列需要加锁,性能损失极大,而且不能很好的保证缓存亲和度。

多队列多处理器调度(MQMS)
每个CPU有一个队列,可以减小加锁损失,且具有良好的缓存亲和度,问题是不同队列负载不均衡。
实现负载均衡的方法是让工作跨CPU迁移,一种具体实现是工作窃取技术:工作量较少的队列不定期地“偷看”其他队列,如果目标队列比源队列更满,就“窃取”一个或多个工作。

现代操作系统的调度算法有O(1)调度程序、完全公平调度程序(CFS)以及BF调度程序(BFS)等。

2 内存虚拟化

2.1 抽象:地址空间

早期系统只运行一个程序,内存简单分为操作系统部分和程序部分。

后来出现运行多道程序的时分系统,最简单的方式是CPU程序切换时,将程序内存全部保存到硬盘上,再将下一个程序从硬盘加载到内存中。缺点是读写硬盘效率低下。

可以在进程切换时,仍将进程信息保留在内存中,每个进程拥有一部分内存,如下图所示:
在这里插入图片描述
此时就出现了内存保护的问题:我们不希望一个程序读、写另一个程序的内存。所以操作系统抽象出了地址空间,即一个进程可见的内存,进程的地址空间包含程序的所有内存状态,包括代码、堆、栈等,如下图:
在这里插入图片描述
地址空间是操作系统提供的内存抽象,是从0开始的连续空间,而程序可能加载在物理内存的任意位置,操作系统需要将地址空间中的虚拟地址与物理内存地址对应起来。这便是内存虚拟化的关键。

虚拟内存的实现有3个目标:

  • 透明:程序感知不到内存虚拟化的存在,就和独占一整个物理内存一样
  • 效率:包括时间效率和空间效率
  • 保护:一个进程不会影响其他的进程,也不影响操作系统本身

内存操作API:

  • 进入函数时,会在栈上分配函数内的局部变量,函数退出时释放内存
  • malloc() 分配堆内存
  • free() 释放堆内存
  • 其他还有 brk、sbrk、mmap、calloc、realloc 等

2.2 机制:地址转换

地址转换:硬件对每次地址访问进行处理,将指令中的虚拟地址转换为实际物理地址。

动态重定位:每个CPU需要两个寄存器,基址寄存器和界限寄存器,基址寄存器存储进程在物理内存中的实际加载地址,此时,物理地址 = 虚拟地址 + 基址。而界限寄存器提供了访问保护,若进程访问超过界限的地址,CPU会发生异常。

CPU的这个负责地址转换的部分统称为内存管理单元(MMU)

动态重定位存在资源浪费:必须将进程的地址空间完整的加载到连续的物理内存上。

分段:将地址空间分为代码、栈、堆等不同逻辑段, MMU为每个逻辑段分配一对基址和界限寄存器。这样,只有已使用的内存才在物理内存中分配空间。
在这里插入图片描述
段错误:在支持分段的机器上内存访问超过界限。也用于不支持分段的机器。

分段的地址转换方式:

  • 显式方式:用虚拟地址的开头几位来标识不同的段。
  • 隐式方式:硬件通过地址产生的方式来确定段。例如,如果地址由程序计数器产生,那么地址在代码段。如果基于栈或基址指针,它一定在栈段。其他地址则在堆段。

栈是反向增长的,所以硬件除了记录基址和界限,还需要记录段的增长方向。

支持共享:硬件为每个段设置保护位,标记是否可读写、执行,并检查程序内存访问是否允许。这样不可写的段就可以被多个进程共享。

分段粒度:上述的分为代码、栈、堆三个段是粗粒度分段。早期有的系统会划分大量较小的段,称为细粒度分段,这种分段需要进一步的硬件支持,并在内存中保存某种段表。

分段的操作系统支持:

  1. 上下文切换时,各个段寄存器中的内容必须保存和恢复
  2. 管理物理内存的空闲空间。由于每个程序的每个段大小不同,物理内存中会有很多不连续的空闲空间,这种问题被称为外部碎片。一种解决方案是紧凑物理内存,重新安排原有的段。另一种是利用空闲列表管理算法,试图保留大的内存块用于分配。

空闲空间管理
管理空闲空间的数据结构称为空闲列表,记录哪些空间还没有分配,如下所示:
在这里插入图片描述
在这里插入图片描述
分配内存时,从空闲列表找到合适的空间,并更新列表。释放内存时,会对列表进行合并。

查找可用空间的具体策略有最优匹配、最差匹配、首次匹配、下次匹配、分离空闲列表、伙伴算法等。

2.3 分页

分页:将进程的地址空间分割成固定大小的单元,每个单元称为一页。相应地,把物理内存看成是定长槽块的阵列,叫作页帧。每个页帧包含一个虚拟内存页。
示例:
在这里插入图片描述

在这里插入图片描述

页表:操作系统为每个进程创建的数据结构,记录地址空间的每个虚拟页放在物理内存中的位置。

典型页的大小一般为4KB。

两个进程可以共享同一物理页,比如代码页。

快速地址转换TLB:
TLB是虚拟到物理地址转换的硬件缓存,会缓存页表中的部分映射关系。
由于TLB只对当前进程生效,所以上下文切换时,要么清空TLB,要么TLB支持多进程,方式是在TLB中添加表示进程的地址空间标识符。
替换策略:RLU(最近最少使用)或随机策略。

页表实现方式:

  • 线性页表:基于数组的页表,占用内存大。
  • 大型页:可减少TLB内存占用,但是会导致内部碎片。
  • 分段+分页:为进程的每个逻辑分段(代码、堆和栈)提供一个页表。MMU中的基址寄存器保存分段的页表的物理地址,界限寄存器用于指示页表的结尾。
  • 多级页表:树形结构,将页表分成页大小的单元,如果整页无效,就不分配该页的页表,然后使用页目录记录页表单元。
  • 反向页表:物理页映射到虚拟页,整个系统只有一个,并建立散列表。

2.4 交换空间

程序的地址空间超过物理内存大小时,需要将一部分地址空间存到磁盘等大而慢的设备上。

交换空间: 在硬盘上开辟一部分空间用于物理页的移入和移出。

如果一个物理页已被交换到硬盘,访问该页会产生页错误,操作系统会将该页交换到内存中。

页交换策略: 决定哪些页被交换出内存,具体策略有FIFO、随机、LRU、近似LRU等。

3 并发

3.1 并发介绍

线程:进程可以有多个线程,和进程类似,每个线程有自己的程序计数器、寄存器,线程切换会发生上下文切换。一个进程的每个线程有自己的栈空间,共享堆空间。

共享数据
以下代码,假设两个线程各执行一次mythread(),执行1000万次+1操作,那么预期结果应该是2000万。

static volatile int counter = 0;void mythread()
{for (int i = 0; i < 1e7; i++) {counter = counter + 1;}
}

然而实际的结果可能不是2000万,原因是,counter = counter + 1 实际的汇编代码可能是:

mov 0x8049a1c, %eax 
add $0x1, %eax
mov %eax, 0x8049a1c

这个例子假定,变量counter位于地址0x8049a1c。在这3条指令中,先用x86的mov指令,从内存地址处取出值,放入eax。然后,给eax寄存器的值加1(0x1)。最后,eax的值被存回内存中相同的地址。

假设counter=50,线程1先执行前2条汇编指令,此时eax=51,然后发生时钟中断,切换到线程2运行。
线程2执行了全部的3条指令,将共享变量counter设为51(每个线程都有自己的专用寄存器)。
然后又发生了一次上下文切换,线程1恢复运行,线程1的eax=51,执行mov,counter再次被设置为51。
所以,counter = counter + 1 代码执行了两次,counter的值却只增加了1。

此段代码称为临界区,即访问共享资源的代码片段,资源通常是一个变量或数据结构。

这种情况称为竞态条件,出现在多个线程同时进入临界区时,它们都试图更新共享资源,导致了意外的结果。

不确定性程序由一个或多个竞态条件组成,程序的输出因运行而异,具体取决于哪些线程在何时运行。

我们真正想要的代码就是所谓的互斥。这个属性保证了如果一个线程在临界区内执行,其他线程将被阻止进入临界区。

原子性:原子性是指作为一个单元,要么全部执行,要么没有执行。以上例子,如果counter = counter + 1是一个原子操作,则不会有不确定性问题。

同步原语:指硬件提供的一些指令,用于受控的访问临界区,产生确定的结果。

3.2 锁

:为临界区加锁,保证临界区能够像单条原子指令一样执行。

1    lock_t mutex; // 锁,为全局变量
2    ...
3    lock(&mutex); // 获取锁
4    balance = balance + 1; // 临界区代码
5    unlock(&mutex); // 释放锁

锁变量保存了锁在某一时刻的状态,要么是unlocked,表示没有线程持有锁,要么是locked,表示有一个线程持有锁。锁也会保存其他的信息,比如持有锁的线程,或请求获取锁的线程队列。

调用lock()尝试获取锁,如果没有其他线程持有锁,该线程会获得锁,进入临界区。如果有其他线程持有锁,该线程会等待。 持有锁的线程调用unlock()释放锁,此时如果有等待线程,其中一个会获取该锁,进入临界区。

锁评价标准

  1. 提供互斥,能够阻止多个线程进入临界区
  2. 公平性,竞争线程有公平的机会抢到锁,或者说是否有竞争锁的线程会饿死,一直无法获得锁
  3. 性能,有几种场景:只有一个线程、一个CPU上多个线程竞争、多个CPU上多个线程竞争

锁的实现方式

锁由硬件提供的同步原语以及操作系统共同实现。硬件原语有:

  • 控制中断
    在单处理器系统上,进入临界区之前关闭中断,可以保证临界区的代码不会被中断,从而原子地执行,结束之后重新打开中断,程序正常运行。
    问题是程序可以通过关闭中断独占处理器、不支持多处理器、关闭中断导致中断丢失。
  • test-and-set 指令
    硬件提供test-and-set(原子交换)指令,检查锁的状态是否是unlocked,如果是,则设置为locked。如果不满足,则循环检查。由于该指令是原子的,所以可以满足互斥。
    这种循环检查的锁称为自旋锁
  • compare-and-swap 指令
    该指令检测指针指向的值是否和expected相等;如果是,更新指针所指的值为新值,否则,什么也不做。该指令同样可以实现自旋锁。
  • 链接的加载(load-linked)和条件式存储(store-conditional)指令
  • 获取并增加(fetch-and-add)指令
    这种方式可以保证所有线程都抢到锁。

自旋锁由于一直自旋占用CPU,可能会产生性能问题,解决方式有:

  • 获取锁失败、要自旋的时候,通过yield()系统调用取消调度,让出CPU。
  • 使用等待队列:获取锁失败时,线程睡眠,并加入到等待队列中,锁被释放时,唤醒等待队列中的下一个线程。
  • 两阶段锁:第一阶段先自旋一段时间,希望可以获取锁,如果没有获得锁,第二阶段调用者会睡眠,直到锁可用。Linux就是这种锁,不过只自旋一次。

基于锁的并发数据结构

  • 懒惰计数器
    通过多个局部计数器和一个全局计数器来实现一个逻辑计数器,其中每个CPU核心有一个局部计数器,每个局部计数器有一个锁,全局计数器有一个锁。
    线程只增加局部计数器。局部计数器定期转移给全局计数器,并将自己清0。
  • 并发链表
    链表只有一把锁,插入、查找、删除等操作加锁。
  • 并发队列 (链表实现)
    有两个锁,一个锁队列头,一个锁队列尾,支持入队和出队操作。
  • 并发散列表 (不需要扩展大小)
    每个散列桶(每个桶都是一个链表)有一个锁。

3.3 条件变量

在很多情况下,线程需要检查某一条件满足之后,才会继续运行,比如父线程需要检查子线程是否执行完毕 (称为join)。

当某些条件不满足时,线程把自己加入等待队列。其他线程改变了上述条件时,唤醒一个或者多个等待线程,让它们继续执行。这种思想称为条件变量

条件变量有两种相关操作:wait()signal()。线程要睡眠的时候,调用wait()。当线程想唤醒等待在某个条件变量上的睡眠线程时,调用signal()。调用signal和wait时要持有锁。

int done = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t c = PTHREAD_COND_INITIALIZER;void thr_exit() {Pthread_mutex_lock(&m);done = 1;Pthread_cond_signal(&c);Pthread_mutex_unlock(&m);
}void thr_join() {Pthread_mutex_lock(&m);while (done == 0)Pthread_cond_wait(&c, &m);Pthread_mutex_unlock(&m);
}

生产者/消费者(有界缓冲区)问题:
有一个或多个生产者线程和一个或多个消费者线程。生产者把生成的数据项放入缓冲区;消费者从缓冲区取走数据项。当缓冲区满时,生产者等待,当缓冲区空时,消费者等待。

覆盖条件:用 pthread_cond_broadcast() 代替上述 pthread_cond_signal(),唤醒所有的等待线程

3.4 信号量

信号量(sem_t)是有一个整数值的对象,需要设置初始值

int sem_wait(sem_t * s){信号量的值减1若值为非负数,直接返回,否则等待
}
int sem_post(sem_t * s){信号量的值加1如果有等待线程,唤醒其中一个
}

由此可知,信号量值为负数时,表示等待线程的个数。

二值信号量(锁)
把临界区用一对sem_wait()/sem_post()环绕,并将信号量初始化为1,这样就能实现锁。

信号量用作条件变量
一个线程等待条件成立,另外一个线程修改条件并发信号给等待线程,从而唤醒等待线程。

生产者/消费者(有界缓冲区)问题
用两个信号量empty和full分别表示缓冲区空或者满,并用二值信号量加锁。

3.5 常见并发问题

违反原子性缺陷:
代码段本意是原子的,但在执行中并没有强制实现原子性,案例:

Thread 1::
if (thd->proc_info) {fputs(thd->proc_info, ...);
}
Thread 2::
thd->proc_info = NULL;

非空检查和fputs()是假设原子的,当假设不成立时,代码就出问题了。

错误顺序缺陷:
不同线程的内存访问的预期顺序被打破了。

死锁缺陷:
死锁的四个条件:

  • 互斥:线程对于需要的资源进行互斥的访问。
  • 持有并等待:线程持有了资源,同时又在等待其他资源。
  • 非抢占:线程获得的资源,不能被抢占。
  • 循环等待:线程之间存在一个环路,每个线程持有一个资源,而这个资源又是下一个线程要申请的。

预防死锁:

  • 循环等待:按固定顺序加锁。全序:设定全部锁的加速顺序。偏序:设定部分锁的加锁顺序。
  • 持有并等待:为抢锁的过程加锁。
  • 非抢占:获取锁失败时,释放已获取的所有的锁。
  • 互斥:使用硬件的原子指令,避免需要加锁。

避免死锁:可以通过处理器调度来避免死锁,如银行家算法。

检查和恢复:允许死锁偶尔发生,检查到死锁时再采取行动。

3.6 基于事件的并发

等待事件发生;当它发生时,检查事件类型,然后做出处理;这是最基础的事件循环:

while (1) {events = getEvents(); for (e in events)processEvent(e);
}

存在的问题:

  • 事件处理程序不允许阻塞调用,I/O需要使用异步I/O。
  • 需要手动管理状态,比如发出异步I/O时,必须打包一些程序状态,以便下一个事件处理程序在I/O最终完成时使用。
  • 和使用多线程相比,增加了系统的复杂性。

4 持久性

4.1 I/O设备

典型系统架构:
在这里插入图片描述
总线分层设计,高性能设备离CPU更近,而外围总线可连接的设备更多。

一个标准设备可分为接口内部结构两部分,接口通常由3个寄存器组成:

  • 状态寄存器,可以读取并查看设备的当前状态
  • 命令寄存器,用于通知设备执行某个具体任务
  • 数据寄存器,将数据传给设备或从设备接收数据

通过读写这些寄存器,操作系统可以控制设备的行为:

  • 轮询,操作系统轮询设备的状态,等待设备就绪
  • 中断,设备就绪时抛出硬件中断,引发CPU跳转执行预定义的中断处理程序

利用DMA进行高效数据传送:DMA引擎是一个特殊设备。操作系统告诉DMA数据在内存的位置,要拷贝的大小以及要拷贝到哪个设备。数据传输完成后,DMA会抛出中断通知操作系统。

两种设备交互方式

  • I/O指令,操作系统调用I/O指令,指定一个存入数据的特定寄存器及一个代表设备的特定端口。
  • 内存映射I/O,硬件将设备寄存器作为内存地址提供,操作系统通过该内存地址读写设备数据。

设备驱动程序:
封装设备交互的细节,为操作系统提供抽象接口。
驱动程序占据了操作系统大部分的代码,且是系统崩溃的主要原因。

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

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

相关文章

前端常用的几种算法的特征、复杂度、分类及用法示例演示

算法&#xff08;Algorithm&#xff09;可以理解为有基本运算及规定的运算顺序所构成的完整的解题步骤&#xff0c;或者看成按照要求设计好的有限的确切的计算序列&#xff0c;并且这样的步骤和序列可以解决一类问题。算法代表着用系统的方法描述解决问题的策略机制&#xff0c…

嵌入式Linux之MX6ULL裸机开发学习笔记(IMX启动方式-启动设备的选择)

一,硬件启动方式选择 1.启动方式的选择 6ull支持多种启动方式。 比如可以从 SD/EMMC、 NAND Flash、 QSPI Flash等启动。 6ull是怎么支持多种外置flash启动程序的。 1.启动方式选择&#xff1a; BOOT_MODE0 and BOOT_MODE1&#xff0c;这两个是两个IO来控制的&#xff0c;…

C#上位机与欧姆龙PLC的通信09----开发专用的通讯工具软件(Winform版)

1、介绍 上节文章已经完成了通讯库的开发&#xff0c;可以看到库还是蛮厉害的&#xff0c;在项目中就可以直接拿来应用&#xff0c;这节要做的就是做一个工具软件&#xff0c;形成自己专业的通讯工具&#xff0c;也是对通讯库的直接利用&#xff0c;本节要写的工具软件是一个w…

【HBase】——优化

1 RowKey设计 重要&#xff1a;一条数据的唯一标识就是 rowkey&#xff0c;那么这条数据存储于哪个分区&#xff0c;取决于 rowkey 处于 哪个一个预分区的区间内&#xff0c;设计 rowkey的主要目的 &#xff0c;就是让数据均匀的分布于所有的 region 中&#xff0c;在一定程度…

如何查找iPhone中所有的应用程序

​ ​ Apple 的 App Store 共有约 200 万个适用于 iPhone 和 iPad 的应用程序。如果您像我们一样&#xff0c;您的 iOS 或 iPadOS 设备上可能有数十个应用程序&#xff0c;但没有机会将它们全部整理好。您很容易忘记主屏幕上应用程序图标的位置。 幸运的是&#xff0c;iPhone…

2024年软件测试行业如何发展呢?测试人该怎么办?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、表面"衰落…

亚马逊店铺遇到账号申诉模版分享

1.表达诚意&#xff0c;先认错再说&#xff1a;我知道&#xff0c;最近我们在Amazon.com上作为卖家的表现已经低于亚马逊和我们自己的质量标准。 2.清楚分明的格式&#xff1a;我们库存管理的混乱导致了延迟发货&#xff0c;更糟糕的是&#xff0c;物品无法使用。当延迟发货和…

杰发科技AC7801——IO模拟IIC注意事项

7801的参考手册没有说清楚 7840说明了用开漏 使用办法

【力扣每日一题】1944队列中可以看到的人数

目录 题目来源 题目描述 示例 提示&#xff1a; 思路分析 总结 代码实现 java实现 c实现 得分情况 java c p.s.吐槽一点无足轻重的事情 题目来源 力扣1944队列中可以看到的人数 题目描述 有 n 个人排成一个队列&#xff0c;从左到右 编号为 0 到 n - 1 。给你以…

jupyter更改默认路径到其它的目录或者到其它的盘 比如D盘

1.打开终端 输入jupyter notebook --generate-config 如下 2.在C:\Users\mb5958\.jupyter路径下 3.用记事本打开它&#xff0c;搜索directory 4.在你想要的路径下新建一个文件夹&#xff0c;如‘D:\jupyterFile’&#xff0c;然后将路径名放在c.NotebookApp.notebook_dir"…

appium自动化问题总结

1、DeprecationWarning: find_element_by_name is deprecated. Please use find_element(byBy.NAME, valuename) instead self.driver.find_element_by_name appium软件查看不到新建session的工具时候&#xff0c;可以下载2020年版本的appim&#xff0c;然后安装下面地址操作配…

leaflet呼吸闪烁效果

leaflet呼吸闪烁效果 1.功能背景 这个效果一把用于点击选中&#xff0c;报警提升效果。 2.功能开发 2.1 marker 这个效果还是很好实现&#xff0c;主要通过计时器设置透明度的组合实现。 function setTargetSelect(e){var i 1var int setInterval(() > {if(!e._map…

从vue小白到高手,从一个内容管理网站开始实战开发第三天,使用Element UI构建页面-登录(一)

上次我们介绍了如何安装Element UI库,这次我们使用Element UI中的组件开始开发我们的页面。 开发之前要先在项目中建立好几个目录,方便我们下面的开发。 一、在项目中创建页面管理目录 1、pages目录(文件夹) 首先在src文件夹下创建一个名为pages的文件夹,该文件夹用来统…

KeyError: ‘mistral‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

打开软件游戏提示缺少msvcp140.dll的解决方法,修复缺少140dll文件的方法

一、msvcp140.dll是什么文件&#xff1f; msvcp140.dll是Microsoft Visual C 2015 Redistributable Package的一部分&#xff0c;它是运行许多Windows应用程序所必需的动态链接库文件之一。它包含了许多C类库&#xff0c;这些类库为开发人员提供了丰富的功能和工具&#xff0c…

罗德与施瓦茨FSVA40信号和频谱分析仪

罗德与施瓦茨FSVA40是一款功能信号和频谱分析仪&#xff0c;适用于从事射频系统的开发、生产、安装和服务的用户。FSVA40信号和频谱分析仪系列始终提供最佳的价格和性能组合&#xff0c;无论是根据最新通信标准测试生产中的无线设备&#xff0c;还是测量低相位噪声、高灵敏度和…

均匀与准均匀 B样条算法

B 样条曲线的定义 p ( t ) ∑ i 0 n P i F i , k ( t ) p(t) \sum_{i0}{n} P_i F_{i, k}(t) p(t)i0∑​nPi​Fi,k​(t) 方程中 n 1 n1 n1 个控制点&#xff0c; P i P_i Pi​, i 0 , 1 , ⋯ n i0, 1, \cdots n i0,1,⋯n 要用到 n 1 n1 n1 个 k k k 次 B 样条基函数 …

ChatGPT大升级,文档图像识别领域迎来技术革新

​写在前面ChatGPT迎来重大升级冲击与机遇并存​大模型时代的思考与探索■ 像素级OCR统一模型- UPOCR■ OCR大一统模型- SPTS v3■ 文档识别分析LLM应用 写在最后问卷抽奖 ​写在前面 2023 年 12 月 31 日第十九届中国图象图形学学会青年科学家会议在广州召开&#xff0c;该会…

记一次 .NET 某新能源材料检测系统 崩溃分析

一&#xff1a;背景 1. 讲故事 上周有位朋友找到我&#xff0c;说他的程序经常会偶发性崩溃&#xff0c;一直没找到原因&#xff0c;自己也抓了dump 也没分析出个所以然&#xff0c;让我帮忙看下怎么回事&#xff0c;那既然有 dump&#xff0c;那就开始分析呗。 二&#xff…

【萤火虫系列教程】2/5-Adobe Firefly 文字​生成​图像

文字​生成​图像 登录账号后&#xff0c;在主页点击文字生成图像的【生成】按钮&#xff0c;进入到文字生成图像 查看图像 在文字生成图像页面&#xff0c;可以看到别人生成的图像。 点击某个图像&#xff0c;就可以进入图像详情&#xff0c;可以看到文字描述。 生成图像 我…