MIT6.828 Lab1 Xv6 and Unix utilities

news2024/11/16 16:34:05

2023MIT6.828 lab-1

官方地址

一、sleep

实验内容

  • 调用sleep(系统调用)编写用户级别程序
  • 能暂停特定时常的系统滴答
  • 程序保存在user/sleep.c

实验过程

xv6的参数传递

查看官方文档提示的文件中,多采用如下定义:

int main(int argc, char *argv[])
在xv6操作系统中:
1、argc 是一个常见的参数,用于表示传递给程序的命令行参数的数量。 
2、argv(argument vector)是一个指向字符指针数组的指针,该数组中的每个元素都是一个指向命令行参数的字符串的指针。
3、程序的内部可通过argc和argv来访问外界输入的参数
注意:
1、argc指示出程序执行时传入的参数个数,常可用来判断输入是否符合要求
2、argv[]为存放字符指针的数组,我们需要的是其指向地址存放的数据,若要当整型数据使用还需进行相应转换

实现代码

#include "kernel/types.h"     //调用相应的头文件
#include "kernel/stat.h"
#include "user/user.h"
int main( int argc ,char *argv[])  //通过argc和argv传入参数
{
	if(argc!=2)   //传入参数数量不符合
	{
	  fprintf(2,"usage:sleep time\n");
	  exit(1);
	}
sleep(atoi(argv[1]));  //由于argc[]指向字符型数据,调用atoi()进行转换
fprintf(2,"nothing happens for a little while\n");
exit(0);  //退出程序
}

编译准备

在make qemu之前,还需进行将编写的sleep()程序加入编译文件中:

1、进入lab目录下
vi Makefile  //打开文件
: ?UPROGS   //vim下搜索关键词

2、定位到UPROGS,在最后一处按格式补入sleep():
UPROGS=\
        $U/_cat\
        $U/_echo\
        $U/_forktest\
        $U/_grep\
        $U/_init\
        $U/_kill\
        $U/_ln\
        $U/_ls\
        $U/_mkdir\
        $U/_rm\
        $U/_sh\
        $U/_stressfs\
        $U/_usertests\
        $U/_grind\
        $U/_wc\
        $U/_zombie\
        $U/_sleep\

3、保存退出

编译运行

make qemu

xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ sleep 10
nothing happens for a little while  //停顿后显示,达到实验效果

工具测试

在lab目录下执行:
$ ./grade-lab-util sleep
或
$ make GRADEFLAGS=sleep grade

输出:
在这里插入图片描述
测试通过

二、pingpong

实验内容

  • 在父进程中创建子进程
  • 编写用户级程序调用系统调用通过一对pipes在两个进程间实现’‘ping-pong’’ 一个字节
  • 父进程给子进程发送一字节,子进程收到后print pid: received ping 这里的pid is its process ID,并通过pipes给父进程传递一字节,然后exit
  • 父进程读取来自子进程的字节,并print pid: received pong,然后exit
  • 程序保存在user/pingpong.c

实验过程

fork用法

打开/kernel/proc.c查看fork()源码

// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }

  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  np->sz = p->sz;

  // copy saved user registers.
  *(np->trapframe) = *(p->trapframe);

  // Cause fork to return 0 in the child.
  np->trapframe->a0 = 0;

  // increment reference counts on open file descriptors.
  for(i = 0; i < NOFILE; i++)
    if(p->ofile[i])
      np->ofile[i] = filedup(p->ofile[i]);
  np->cwd = idup(p->cwd);

  safestrcpy(np->name, p->name, sizeof(p->name));

  pid = np->pid;

  release(&np->lock);

  acquire(&wait_lock);
  np->parent = p;
  release(&wait_lock);

  acquire(&np->lock);
  np->state = RUNNABLE;
  release(&np->lock);

  return pid;
}

该函数的作用:
1、创建一个新进程,并复制父进程的内容
2、为子进程(child process)设置内核栈(kernel stack),以便在子进程执行完毕后,其行为表现得就像是从 fork() 系统调用返回一样
3、父子进程内容相同(除个别数据),但内存空间不同,修改互不影响
4、在父进程中,fork()返回新创建的子进程的进程ID;在子进程中,fork()返回0;如果发生错误,则返回-1

结构体proc的声明
该结构体存储了每一个进程的信息

// Per-process state
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // wait_lock must be held when using this:
  struct proc *parent;         // Parent process

  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Virtual address of kernel stack
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // User page table
  struct trapframe *trapframe; // data page for trampoline.S
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
};

pipes用法

pipe()函数定义

int pipe(int p[])  Create a pipe, put read/write file descriptors in p[0] and p[1]. 

1、调用pipe函数来创建一个新的管道。如果成功,pipe函数返回0,并将两个文件描述符分别存放在p[0]和p[1]中。如果失败,返回-1。
2、pipe为半双工通信,要根据实际指定通信方向,关闭管道的读或写,保留另一功能
3、可采用两条pipe实现互通
在这里插入图片描述

getpid用法

获取当前进程PID
int getpid()  Return the current process’s PID. 

实现代码

注意:读写顺序
#include "kernel/types.h"
#include "user/user.h"

int main(int argc,int *argv[])
{
  int ptc_pipe[2];
  int ctp_pipe[2];

  pipe(ptc_pipe); //父to子
  pipe(ctp_pipe);//子to父

  int pid;
  pid = fork(); //新建子进程

  if(pid==0) //处于子进程
  {
     //设定管道通信方向
     close(ptc_pipe[1]); //子读取
     close(ctp_pipe[0]); //子写入

     char buff[16];
     if( read( ptc_pipe[0],buff,1) ==1 ) //子进程收到父进程的一字节
     {
        printf("%d: received ping\n",getpid() );
     }

     write( ctp_pipe[1],"p",1 ); //往父进程发送一字节
     exit(0);
  }
  else //处于父进程
  {
        //设置管道方向
        close( ptc_pipe[0] );// 父写入
        close( ctp_pipe[1] );// 父读取

        write( ptc_pipe[1],"p",1 ); //往子进程发送一字节

        char buff[16];
        if( read( ctp_pipe[0],buff,1)==1 )//父进程读取到一字节
        {
          printf("%d: received pong\n",getpid() );
        }
  }
exit(0);
}

编译准备

往Makefile中UPROGS=\项目添加  $U/_pingpong\

运行结果

在这里插入图片描述

工具测试

make GRADEFLAGS=pingpong grade

在这里插入图片描述
测试通过

三、primes

实验内容

1、在xv6中使用pipe编写一个并发的素数筛选程序
2、使用pipe和fork来建立管道
3、第一个进程向管道内输入数字2到35
4、对于每个素数,创建一个进程来从管道左侧读取,并传输给另一条管道右侧
5、受限于xv6的有限文件描述符和进程,第一个进程将在35处停止
6、程序位于user/primes.c

实验过程

过程分析

1、采用pipe进行数字传递,用fork创建下一级,直到结束
2、每级中挑选出一个素数,把剩余经过此素数处理过的传递到下一级
3、传递后父进程要执行等待子进程结束的操作

实现代码

注意:
1、仔细关闭程序不需要的文件描述符,否则在35前xv6的资源会耗尽
2、pipe用完关闭,避免拥塞
3、注意pipe和fork执行顺序

#include "kernel/types.h"
#include "user/user.h"

int children( int ptc_pipe[])
{
  //设置管道方向
  close( ptc_pipe[1] );//子读取,不需要写入

  //对父到子管道进行检查
  int num;
  if( read( ptc_pipe[0] ,&num,sizeof(num)) == 0 ) //管道空
  {
         close( ptc_pipe[0]);
          exit(0);
  }
  else
  {
    printf("prime %d\n",num); //打印素数
  }
  
  //创建孙进程
  int pid;
  int ctg_pipe[2];
  pipe( ctg_pipe );//子进程到孙进程管道

  pid=fork();
  if( pid==0 ) //孙子进程
  {
    children( ctg_pipe );  //递推

  }
  else  //子进程
  {
     //设置子到孙管道方向
     close( ctg_pipe[0] );//子写孙读
     int i;
     while( read( ptc_pipe[0],&i,sizeof(i))>0 ) //管道还有数据
     {
       if( i % num !=0 )//有余数
       write( ctg_pipe[1],&i,sizeof(i) );//发送到下一级
     }
     close( ctg_pipe[1]);//关闭写,避免读拥塞
     wait(0); //等待孙进程结束

  }
  exit(0);//结束返回

}
int main(int argc,int *argv[])
{
    int ptc_pipe[2];
        //建立第一个管道
    pipe(ptc_pipe);

    int pid;
    pid =fork(); //创建子进程

    if( pid==0 ) //位于子进程
    {
       children(ptc_pipe); //函数反复调用
    }
    else //位于父进程
    {
       //管道设置
       close(ptc_pipe[0]); //父写入,不需要读取

       for(int i=2;i<=35;i++) //往管道输入数字
       {
        write(ptc_pipe[1],&i,sizeof(i));
       }
       close( ptc_pipe[1]);//关闭写,避免读拥塞
       wait(0);//等待子进程
    }
exit(0);
}

编译准备

往Makefile中UPROGS=\项目添加  $U/_primes\

运行结果

在这里插入图片描述

工具测试

 make GRADEFLAGS=primes grade

在这里插入图片描述
测试通过

四、find

实验内容

  1. 为Xv6编写一个简单版本的UNIX查找程序
  2. 通过目录树的形式找到特定的名称
  3. 程序保存在user/find.c

实验过程

线索提示

  1. 查看user/ls.c如何实现读目录
  2. 使用递归操作时find能深入子目录
  3. 使用make clean去获得干净的文件系统,然后make qemu
  4. 使用C语言的字符串
  5. 使用strcmp()对比字符串,而不是==

ls.c源码

fmtname用于格式化文件名
char*
fmtname(char *path)
{
  static char buf[DIRSIZ+1];
  char *p;

  // Find first character after last slash.
  for(p=path+strlen(path); p >= path && *p != '/'; p--)
    ;
  p++;

  // Return blank-padded name.
  if(strlen(p) >= DIRSIZ)
    return p;
  memmove(buf, p, strlen(p));
  memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));
  return buf;
}
ls为主要部分
void
ls(char *path)
{
  char buf[512], *p;
  int fd;
  struct dirent de;
  struct stat st;

  if((fd = open(path, O_RDONLY)) < 0){
    fprintf(2, "ls: cannot open %s\n", path);
    return;
  }

  if(fstat(fd, &st) < 0){
    fprintf(2, "ls: cannot stat %s\n", path);
    close(fd);
    return;
  }

  switch(st.type){
  case T_DEVICE:
  case T_FILE:
    printf("%s %d %d %l\n", fmtname(path), st.type, st.ino, st.size);
    break;

  case T_DIR:
    if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
      printf("ls: path too long\n");
      break;
    }
    strcpy(buf, path);
    p = buf+strlen(buf);
    *p++ = '/';
    while(read(fd, &de, sizeof(de)) == sizeof(de)){
      if(de.inum == 0)
        continue;
      memmove(p, de.name, DIRSIZ);
      p[DIRSIZ] = 0;
      if(stat(buf, &st) < 0){
        printf("ls: cannot stat %s\n", buf);
        continue;
      }
      printf("%s %d %d %d\n", fmtname(buf), st.type, st.ino, st.size);
    }
    break;
  }
  close(fd);
}

int
main(int argc, char *argv[])
{
  int i;

  if(argc < 2){
    ls(".");
    exit(0);
  }
  for(i=1; i<argc; i++)
    ls(argv[i]);
  exit(0);
}
 ls函数中使用的结构体信息
//用于表示一个目录项
struct dirent { 
 ushort inum; // 本目录下该文件所在的inode号 
 char name[DIRSIZ]; // 该文件的文件名 
 };
struct stat { short type; // 文件类型 
			  int dev; // 设备号 
			  uint ino; // inode编号 
			  short nlink; // 链接数 
			  uint size; // 文件大小(字节) 
			  time_t atime; // 最后访问时间 
			  time_t mtime; // 最后修改时间 
			  time_t ctime; // 最后状态改变时间 
			  int uid; // 用户ID 
			  int gid; // 组ID 
			  short rdev; // 特殊设备类型 
			  uint flags; // 文件标志 
			  uint pad[4]; // 填充字段,确保结构大小对齐 
			  };
memmove函数
void *memmove(void *dest, const void *src, size_t n);
//dest:指向目标内存区域的指针,即要将数据移动到的位置。
//src:指向源内存区域的指针,即要从中复制数据的起始位置。
//n:要复制的字节数。
 open函数
int open(const char *path, int flags);
//`path`:要打开的文件或设备的路径名。
//`flags`:指定打开文件的方式,例如 `O_RDONLY`(只读)、`O_WRONLY`(只写)、`O_RDWR`(读写)等。
//返回值:非负文件描述符
//       失败返回-1
 fstat函数
int fstat(int fd, struct stat *buf);
//fd:文件描述符,即之前通过 open 系统调用获得的用于标识文件的整数。
//buf:一个指向 stat 结构体的指针,该结构体用于存储获取到的文件状态信息。
//返回值:0成功,-1失败

实现代码

实现思路:

  • 通过层层搜索
  • 遇到文件则判断输出
  • 遇到目录则延长到子目录采用递归搜寻
  • 在ls.c基础上修改
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
//#include "kernel/fcntl.h"

char*
fmtname(char *path)
{
  static char buf[DIRSIZ+1];
  char *p;

  // Find first character after last slash.
  for(p=path+strlen(path); p >= path && *p != '/'; p--) //从后往前查找'/'
    ;
  p++; //+1为'/'后第一个字符

  // Return blank-padded name.
  if(strlen(p) >= DIRSIZ)
    return p;
  memmove(buf, p, strlen(p)+1); //注意结束符
  return buf;
}

void
findname(char *path,char *findName)
{
  char buf[512], *p; //buf用于存储绝对路径,p用于指向buf操作
  int fd;            //存储文件标识符
  struct dirent de;  //表示目录项
  struct stat st;    //存储文件信息

  //只读打开path路径
  if((fd = open(path, 0)) < 0)
    return; //打开错误

  if(fstat(fd, &st) < 0) //打开错误
  {
    close(fd);
    return;
  }

  switch(st.type)  //顺利开启
  {
  case T_DEVICE:
  case T_FILE:   //path路径为文件
    if( strcmp( fmtname(path),findName)==0) //抽取名称并对比,名称一致
    {
    printf("%s\n", path); //打印路径
    }
    break;


  case T_DIR: //path路径为目录
    //目录名过长
    if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
      printf("find: path too long\n");
      break;
    }
    //复制path到buf//buf记录此刻path的绝对路径
    strcpy(buf, path);
    p = buf+strlen(buf);
    *p++ = '/'; //末尾加分隔符//注意此处指针p的指向buf末尾

    while(read(fd, &de, sizeof(de)) == sizeof(de)) //读取path路径目录内容//read会遍历一遍此目录下的文件
    {
      if(de.inum == 0)  //inode==0
        continue;//不正常的inode,跳出

      //跳过. ..目录
      if(strcmp(de.name, ".") == 0 || strcmp(de.name, "..")==0 )
        continue;

      //复制名称到p头地址的空间  //即在buf后面补名字
      memmove(p, de.name, DIRSIZ);
      p[DIRSIZ] = 0; //地址结束标识符

      findname(buf,findName); //递归查询
    }
    break;
  }
  close(fd);
}

int
main(int argc, char *argv[])
{
  //参数不符合
  if(argc <=2)
  {
    printf("err\n");
    exit(0);
  }
  findname(argv[1],argv[2]);
  exit(0);
}

编译准备

往Makefile中UPROGS=\项目添加  $U/_find\

运行结果

make qemu 

echo > b
mkdir a
echo > a/b
mkdir a/aa
echo > a/aa/b
find . b
root@QQQ:/home/MIT6.828/xv6-labs-2023# make qemu
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

xv6 kernel is booting

hart 1 starting
hart 2 starting
init: starting sh
$ echo > b
mkdir a
echo > a/b
mkdir a/aa
echo > a/aa/b
find . b$ $ $ $ $
./b
./a/b
./a/aa/b
$
符合预期

工具测试

make GRADEFLAGS=find grade
no-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie -march=rv64g -nostdinc -I. -Ikernel -c user/initcode.S -o user/initcode.o
riscv64-unknown-elf-ld -z max-page-size=4096 -N -e start -Ttext 0 -o user/initcode.out user/initcode.o
riscv64-unknown-elf-objcopy -S -O binary user/initcode.out user/initcode
riscv64-unknown-elf-objdump -S user/initcode.o > user/initcode.asm
riscv64-unknown-elf-ld -z max-page-size=4096 -T kernel/kernel.ld -o kernel/kernel kernel/entry.o kernel/kalloc.o kernel/string.o kernel/main.o kernel/vm.o kernel/proc.o kernel/swtch.o kernel/trampoline.o kernel/trap.o kernel/syscall.o kernel/sysproc.o kernel/bio.o kernel/fs.o kernel/log.o kernel/sleeplock.o kernel/file.o kernel/pipe.o kernel/exec.o kernel/sysfile.o kernel/kernelvec.o kernel/plic.o kernel/virtio_disk.o kernel/start.o kernel/console.o kernel/printf.o kernel/uart.o kernel/spinlock.o
riscv64-unknown-elf-objdump -S kernel/kernel > kernel/kernel.asm
riscv64-unknown-elf-objdump -t kernel/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernel/kernel.sym
make[1]: Leaving directory '/home/MIT6.828/xv6-labs-2023'
== Test find, in current directory == find, in current directory: OK (3.1s)
== Test find, recursive == find, recursive: OK (0.9s)
测试通过

五、xargs

实验内容

  • 为xv6编写一个简单版本的uinx xargs程序
  • 它的参数描述了要运行的命令
  • 它从标准输入中读取行
  • 它运行每一行的命令
  • 将此行文本添加到命令参数中
  • 程序存放在user/xargs.c
  • 一个实例:
    • $ echo hello too | xargs echo bye
    • bye hello too
    • $

实验过程

实现方法

  • 从标准输入中读取命令并执行
  • 设置两个数组,一个用于从标准输入中获取每一行的命令及其参数,一个用于将获取的一行指令传递给子程序执行
  • 不断读取标准输入,遇到换行符就执行一次命令,直到标准输入中无数据

exec函数

int exec(char *file, char *argv[]) Load a file and execute it with arguments; only returns if error.
  • 加载一个文件,并按给定参数执行

实现代码

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

#define MAXN 1024

int
main(int argc, char *argv[])
{
    if (argc < 2)
    {
        fprintf(2, "usage: xargs command\n");
        exit(1);
    }

    char *argv_c[MAXARG];    // 存放子进程exec的参数//MAXARG是一个宏定义,用于限制一个进程可以从其命令行参数中获取的最大字节
    for (int i = 1; i < argc; i++)  //去除xargs,保留余下部分
        argv_c[i - 1] = argv[i];

    char buf[MAXN]; // 存放从标准输入转化而来的参数
    char c = 0;
    int stat = 1;   // 从标准输入read返回的状态

    while (stat) // 标准输入中还有数据
    {
        int buf_tail = 0;    // buf尾指针
        int arg_begin = 0;  // 当前这个参数在buf中开始的位置
        int argv_tail = argc - 1;//argv本身还有参数

        while (1)   // 读取一行 //每行执行一次
        {
            stat = read(0, &c, 1);      //从标准输入中读取一个字符//标准输入的文件描述符通常为0

            if (stat == 0) // 标准输入中没有数据,exit
                exit(0);

            if (c == ' ' || c == '\n')  //此字符为空格或换行//意味着单个参数输入结束或开始换行
            {
                buf[buf_tail++] = 0;    //最后一个参数必需为0,否则会执行失败//先0后自增
                argv_c[argv_tail++] = &buf[arg_begin]; //往argv_c中拼接一个参数//字符串操作
                arg_begin = buf_tail;   //更新下一个参数的起点位置
                if (c == '\n')          //每此换行执行一次
                    break;
            }
            else //拼接读取的单个字符
            {
                buf[buf_tail++] = c;
            }
        }

        argv_c[argv_tail] = 0;  //结束标志
        if (fork() == 0)
        {
            exec(argv_c[0], argv_c); //加载命令并执行
        }
        else
        {
            wait(0);
        }
    }
    exit(0);
}

编译准备

往Makefile中UPROGS=\项目添加  $U/_xargs\

运行结果

root@QQQ:/home/MIT6.828/xv6-labs-2023# make qemu
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

xv6 kernel is booting

hart 2 starting
hart 1 starting
init: starting sh
$ echo hello too | xargs echo bye
bye hello too
$ find . b | xargs grep hello
hello
hello
hello
$ sh < xargstest.sh
$ mkdir: a failed to create
$ $ mkdir: c failed to create
$ $ $ hello
hello
hello
$ $

工具测试

riscv64-unknown-elf-ld -z max-page-size=4096 -N -e start -Ttext 0 -o user/initcode.out user/initcode.o
riscv64-unknown-elf-objcopy -S -O binary user/initcode.out user/initcode
riscv64-unknown-elf-objdump -S user/initcode.o > user/initcode.asm
riscv64-unknown-elf-ld -z max-page-size=4096 -T kernel/kernel.ld -o kernel/kernel kernel/entry.o kernel/kalloc.o kernel/string.o kernel/main.o kernel/vm.o kernel/proc.o kernel/swtch.o kernel/trampoline.o kernel/trap.o kernel/syscall.o kernel/sysproc.o kernel/bio.o kernel/fs.o kernel/log.o kernel/sleeplock.o kernel/file.o kernel/pipe.o kernel/exec.o kernel/sysfile.o kernel/kernelvec.o kernel/plic.o kernel/virtio_disk.o kernel/start.o kernel/console.o kernel/printf.o kernel/uart.o kernel/spinlock.o
riscv64-unknown-elf-objdump -S kernel/kernel > kernel/kernel.asm
riscv64-unknown-elf-objdump -t kernel/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernel/kernel.sym
make[1]: Leaving directory '/home/MIT6.828/xv6-labs-2023'
== Test xargs == xargs: OK (4.2s)
root@QQQ:/home/MIT6.828/xv6-labs-2023#
测试通过

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

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

相关文章

如何在nuxt中优雅使用swiper,实现过渡反向+贴合无缝+循环播放【核心代码分析】

视频效果 20240402-1723 图片效果 技术栈 Nuxt3 + Swiper11 Nuxt3 Nuxt: The Intuitive Vue Framework Nuxt Swiper11 Swiper - The Most Modern Mobile Touch Slider (swiperjs.com) 当然你也可以是使用nuxt-swiper Nuxt-Swiper GitHub - cpreston321/nuxt-swiper: Swi…

CPU+GPU+NPU三位一体AI边缘控制器,三屏异显,搭载RK3588处理器

XMS-201采用了Rockchip RK3588八核64位处理器&#xff0c;集成ARM Mali-G610 MP4四核GPU&#xff0c;内置AI加速器NPU&#xff0c;可提供6Tops算力&#xff0c;支持主流的深度学习框架&#xff1b;性能强劲的RK3588可为各类AI应用场景带来更强大的性能表现&#xff0c;适用于机…

【40分钟速成智能风控1】互联网金融风险管理简介

目录 瓦联网金融的发展和现状 风险管理类型划分 欺诈风险 第一方和第三方 账户级和交易级 个人和团伙 互联网金融是传统金融业务与新兴互联网技术结合的一个交叉领域&#xff0c;例如互联网公司开展的金融业务&#xff0c;或者金融机构的线上化服务&#xff0c;都属于互联…

Brain.js 的力量:构建多样化的人工智能应用程序

机器学习&#xff08;ML&#xff09;是人工智能 (AI) 的一种形式&#xff0c;旨在构建可以从处理的数据中学习或使用数据更好地执行的系统。人工智能是模仿人类智能的系统或机器的总称。 机器学习&#xff08;ML&#xff09;与复杂的数学纠缠在一起&#xff0c;让大多数初学者…

08 | Swoole 源码分析之 Timer 定时器模块

原文首发链接&#xff1a;Swoole 源码分析之 Timer 定时器模块 大家好&#xff0c;我是码农先森。 引言 Swoole 中的毫秒精度的定时器。底层基于 epoll_wait 和 setitimer 实现&#xff0c;数据结构使用最小堆&#xff0c;可支持添加大量定时器。 在同步 IO 进程中使用 seti…

three.js能实现啥效果?看过来,这里都是它的菜(01)

经常有老铁问我&#xff0c;这里炫酷效果是如何实现的&#xff0c;还有问我想实现什么效果怎么办&#xff0c;甚至还有想实现动态效果&#xff0c;但是描述不出来的。 好吧&#xff0c;统统满足老铁们呢&#xff0c;本期开始分享three.js效果图&#xff0c;并附带简要简介&…

usbserial驱动流程解析_Part2_初始化流程_以probe为例(echo cat测试回环打印不停问题解决)

usb转串口设备需要注册usb侧和serial侧两侧的操作&#xff0c;本文将简要分析二者的初始化流程以及一些关键函数的初始化流程。 module_init(usb_serial_init); tty设备初始化 内核会直接调用usb_serial_init&#xff0c;开始进行usb和serial的初始化&#xff0c;首先是进行t…

全面探究 LangChain Text Splitters

全面探究 LangChain Text Splitters 0. 引言1. 文本拆分器的类型2. 探究各个文本拆分器2-1. Split by HTML header2-2. Split by HTML section2-3. Split by character2-4. Split code2-5. MarkdownHeaderTextSplitter2-6. Recursively split JSON2-7. Recursively split by ch…

lv17 CGI移植 5-1

简介 CGIC是一个支持CGI开发的开放源码的标准C库&#xff0c;可以免费使用&#xff0c;只需要在开发的站点和程序文档中有个公开声明即可&#xff0c;表明程序使用了CGIC库&#xff0c;用户也可以购买商业授权而无需公开声明。 CGIC能够提供以下功能&#xff1a; 分析数据&a…

疲劳驾驶预警系统项目知识点整理

参考&#xff1a; 重磅&#xff01;头部姿态估计「原理详解 实战代码」来啦&#xff01;-阿里云开发者社区 (aliyun.com) Dlib模型之驾驶员疲劳检测三&#xff08;瞌睡点头&#xff09;_疲劳检测 点头-CSDN博客 python毕业设计 深度学习疲劳检测 驾驶行为检测 - opencv cnn…

面试:HashMap

目录 1、底层数据结构&#xff0c;1.7 与1.8有何不同? 2、为何要用红黑树&#xff0c;为何一上来不树化&#xff0c;树化阈值为何是8&#xff0c;何时会树化&#xff0c;何时会退化为链表? 3、索引如何计算? hashCode都有了&#xff0c;为何还要提供hash()方法?数组容量为…

交换机与队列的简介

1.流程 首先先介绍一个简单的一个消息推送到接收的流程&#xff0c;提供一个简单的图 黄色的圈圈就是我们的消息推送服务&#xff0c;将消息推送到 中间方框里面也就是 rabbitMq的服务器&#xff0c;然后经过服务器里面的交换机、队列等各种关系&#xff08;后面会详细讲&…

保研复习数据结构-图(10)

一.图的定义和基本术语 1.什么是图&#xff1f; 图(Graph)是由顶点的有穷非空集合V(G)和顶点之间边的集合E(G)组成&#xff0c;通常表示为:G(V,E)&#xff0c;其中&#xff0c;G表示图&#xff0c;V是图G中顶点的集合&#xff0c;E是图G中边的集合。 2.什么是完全图&#xf…

【第十二篇】使用BurpSuite实现CSRF(实战案例)

CSRF存在前提:简单的身份验证只能保证请求是发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的 业务场景:新增、删除、收藏、编辑、保存使用Burp发现CSRF漏洞的过程如下。 1、如图,存在修改邮箱的功能点如下: 2、修改邮箱的流量包,此时邮箱已被修改: 思路:是…

12、最小覆盖子串

如何想到这个解法 问题的特点&#xff1a; 首先&#xff0c;认识到这是一个关于子串的问题&#xff0c;而且需要考虑子串的最小长度。这提示我们可能需要使用一种方式来逐步探索不同的子串。滑动窗口的适用性&#xff1a;滑动窗口是处理子串问题的常用技巧&#xff0c;特别是当…

X年后,ChatGPT会替代底层程序员吗?

能不能替代&#xff0c;真的很难说&#xff0c;因为机器换掉人&#xff0c;这其实是一个伦理问题。 其实说白了&#xff0c;任何行业在未来都会被AI或多或少的冲击到&#xff0c;因为ChatGPT做为一个可以持续提升智能的AI&#xff0c;在某些方面的智能程度超过人类并不是什么难…

笛卡尔树[天梯赛二叉树专项训练]

文章目录 题目描述思路AC代码 题目描述 输入样例1 6 8 27 5 1 9 40 -1 -1 10 20 0 3 12 21 -1 4 15 22 -1 -1 5 35 -1 -1 输出样例1 YES 输入样例2 6 8 27 5 1 9 40 -1 -1 10 20 0 3 12 11 -1 4 15 22 -1 -1 50 35 -1 -1 输出样例2 NO思路 见注释 AC代码 #include <bits/st…

5. 4 二重循环将二维数组的某列、某矩形转大写

5. 4 二重循环将二维数组的某列、某矩形转大写 1. 把每一行的b都变成大写 assume cs:codesg,ds:data,ss:stack data segmeNTstr db aaaaabbbbbcccccdb aaaaabbbbbcccccdb aaaaabbbbbcccccdb aaaaabbbbbccccc,$ data endsstack segmentdb 10 dup(0) stack endscodesg SEgments…

【Vue】Vue3中的OptionsAPI与CompositionAPI

文章目录 OptionsAPICompositionAPI对比总结 OptionsAPI 中文名:选项式API通过定义methods,computed,watch,data等属性方法&#xff0c;处理页面逻辑。以下是OptionsAPI代码结构 实例代码: <script lang"ts">// js或者tsimport { defineComponent } from vu…

豆瓣9.7,这部Java神作第3版重磅上市!

Java 程序员们开年就有重磅好消息&#xff0c;《Effective Java 中文版&#xff08;原书第 3 版&#xff09;》要上市啦&#xff01; 该书的第1版出版于 2001 年&#xff0c;当时就在业界流传开来&#xff0c;受到广泛赞誉。时至今日&#xff0c;已热销近20年&#xff0c;本书…