nginx 内存管理(二)

news2024/11/24 11:31:58

共享内存

    • 共享内存结构与接口定义
    • nginx共享内存在操作系统上的兼容性设计
    • 互斥锁
      • 锁的结构体
      • 锁的一系列操作(core/ngx_shmtx.c)
        • 创建锁
      • 原子操作
        • nginx的上锁操作
        • 尝试加锁
        • 获取锁
        • 释放锁
        • 强迫解锁
        • 唤醒等待进程
    • slab共享内存块管理
      • nginx的slab大小规格
      • 内存池结构体
        • 共享内存池结构体slots
        • 分配共享内存池

在这里插入图片描述

共享内存结构与接口定义

正常来说,通过malloc函数申请的内存都是进程私有的内存但是Linux会提供共享内存的系统调用,如mmap和munmap等

Nginx基于Linux提供的系统调用,封装了共享内存的数据结构以及共享内存的创建与释放函数,其共享内存结构和接口定义如下:os/unix/ngx_shmem.h

typedef struct {   
	u_char      *addr;   //指向申请的共享内存块首地址
	size_t       size;    //共享内存块大小
	ngx_str_t    name;    //共享内存块名字
	ngx_log_t   *log;   //共享内存块日志
	ngx_uint_t   exists;//标志是否已经存在
} ngx_shm_t;//共享结构

//以下共享接口
ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);//创建共享内存块
void ngx_shm_free(ngx_shm_t *shm);//释放共享内存块
ngx_int_tngx_shm_alloc(ngx_shm_t *shm)
{    
shm->addr = (u_char *) mmap(NULL, shm->size,  PROT_READ|PROT_WRITE,  MAP_ANON|MAP_SHARED, -1, 0);  //创建
if (shm->addr == MAP_FAILED) { //错误处理
	ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size);       
	return NGX_ERROR;   
}    
return NGX_OK;//成功,返回
}
void ngx_shm_free(ngx_shm_t *shm)
{   
if (munmap((void *) shm->addr, shm->size) == -1)//是否成功
{    //失败处理,记录
   ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "munmap(%p, %uz) failed", shm->addr, shm->size);  
}
}

nginx共享内存在操作系统上的兼容性设计

#if (NGX_HAVE_MAP_ANON)//匿名共享内存
………
#elif (NGX_HAVE_MAP_DEVZERO)//文件共享内存
………
#elif (NGX_HAVE_SYSVSHM)//IPC System V共享内存
………
#endif

互斥锁

  • 并发进程访问共享内存时需要加锁。nginx提供了互斥锁的机制,保证了正确的共享内存的访问。nginx的进程主要是通过ngx_shmtx_t进行加锁、解锁等操作。
  • nginx实现的时候,如果操作系统提供原子操作机制,就使用操作系统的原子操作实现互斥锁,否则nginx采用文件锁实现互斥。

互斥锁模型
在这里插入图片描述

锁的结构体

//core/ngx_shmtx.h
typedef struct {  
	ngx_atomic_t   lock;//0为锁开(空闲),其它(进程号)已上锁
	#if (NGX_HAVE_POSIX_SEM)  //如果有SEM信号量
		ngx_atomic_t   wait;//等待共享内存进程总数
	#endif
} ngx_shmtx_sh_t;

上锁、解锁的结构体模型

typedef struct {
	#if (NGX_HAVE_ATOMIC_OPS) //若有原子操作
	…………
	#if (NGX_HAVE_POSIX_SEM) //如果有信号量 
	…………
	#endif
	#else    
	…………
	#endif    
	ngx_uint_t     spin;
} ngx_shmtx_t;
typedef struct {
	#if (NGX_HAVE_ATOMIC_OPS) //若有原子操作
		ngx_atomic_t  *lock;//进程内指向共享内存锁的地址
		#if (NGX_HAVE_POSIX_SEM) //如果有信号量 
			 ngx_atomic_t  *wait; //指向共享内存等待进程总数
			 ngx_uint_t     semaphore;    //是否使用信号量,1使用
			 sem_t        sem;//sem_t信号量,可用于线程之中,也可用于进程
		#endif
	#else    //操作系统无原子操作和信号量支持,用文件
		ngx_fd_t   fd; 
		u_char        *name;
	#endif    
	ngx_uint_t     spin;
}   ngx_shmtx_t;

锁的一系列操作(core/ngx_shmtx.c)

  1. ngx_int_t ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name);//创建锁
  2. void ngx_shmtx_destroy(ngx_shmtx_t *mtx);//销毁锁
  3. void ngx_shmtx_lock(ngx_shmtx_t *mtx);//获取锁
  4. ngx_uint_t ngx_shmtx_trylock(ngx_shmtx_t *mtx);//尝试加锁
  5. void ngx_shmtx_unlock(ngx_shmtx_t *mtx);//释放锁
  6. ngx_uint_t ngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid);//强制解锁
  7. static voidngx_shmtx_wakeup(ngx_shmtx_t *mtx)//唤醒等进程

临界区管理的基本思路
①找到临界区
②在临界区前面增加一段用于进行检查的代码,当不满足进入临界区的条件,就不进入,直到满足条件才进入,称为进入区(entry section)。
③在临界区后面加上一段称为离开区(exit section)的代码,作为善后处理。基本形式如下:

在这里插入图片描述

创建锁
ngx_int_t ngx_shmtx_create( ngx_shmtx_t *mtx,ngx_shmtx_sh_t *addr, u_char *name);//创建锁

//ngx_shmtx_t *mtx,是进程操作锁结构地址
//ngx_shmtx_sh_t *addr,是共享内存中保存的锁结构地址
//u_char *name,名字(用于区别不同锁)地址

{   
mtx->lock = &addr->lock;  //将共享内存锁信息储存到进程操作锁结构体中
if (mtx->spin == (ngx_uint_t) -1) {        return NGX_OK;    }  
mtx->spin = 2048;//自旋次数指定

#if (NGX_HAVE_POSIX_SEM)   //如果是信号量,初始化sem为1,并将semaphore设为1
	mtx->wait = &addr->wait;  
	if (sem_init(&mtx->sem, 1, 0) == -1) {      
	   ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,                    "sem_init() failed");    } 
	else {        mtx->semaphore = 1;    }
#endif  
return NGX_OK;
}
  • 进程操作锁结构需要获得(保存)共享内存锁的信息,对于自旋锁,
    1)保存共享内存锁的lock;
    2设置自旋锁的自旋次数;以便于后续进行加锁、解锁等操作;
mtx->lock = &addr->lock;  
 if (mtx->spin == (ngx_uint_t) -1) {    //已经加锁了   ?
	return NGX_OK;  
 }
mtx->spin = 2048;//nginx设置的进程自旋次数
  • 对于信号量,
    1)保存共享内存锁的保存wait;
    2设置信号量的semaphore或sem的值;以便于后续进行加锁、解锁等操作;
mtx->wait = &addr->wait;  //保存指向保存共享内存进程总数指针
if (sem_init(&mtx->sem, 1, 0) == -1)   //线程信号量初始化失败
{       
     ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,                      "sem_init() failed");    
} 
else  // 线程信号量初始化成功,初始化semaphore为1
{        
	mtx->semaphore = 1;   //使用信号量
}
  • 其它可能需要记录的调试信息以及可能的错误处理等

原子操作

计算机系统并发的基础

  • 两个原子操作
ngx_atomic_cmp_set(a,old,new):如果*a==old,将*a赋值为new,返回1。否则返回0ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid),(原子操作)若*mtx->lock为0,即将*mtx->lock赋值为ngx_pid。
ngx_atomic_fetch_add(old,v):将*old加上v,并返回*old。

ngx_atomic_fetch_add(mtx->wait, 1),将*mtx->wait加上1,并返回加之前的*mtx->wait值。
nginx的上锁操作
当共享内存lock为0(表示空闲)时可以上锁。对于上锁的操作,Nginx将其标准化为将lock(当其为0时)设为进程的PID。即
*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)
尝试加锁
ngx_uint_tngx_shmtx_trylock(ngx_shmtx_t *mtx)
{   
   return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}
获取锁
  • 进程自旋锁的获取
    • 当共享内存lock为0(表示空闲)时可以上锁。对于上锁的操作,nginx将其标准化为将lock(当其为0时)设为进程的PID。即
      *mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)。

    • 因为*mtx->lock 为0时,可能有很多进程都来上锁,但只能有一个进程会成功上锁。因此对上锁进程来讲,以上上锁操作可能不成功。

    • 此时,当有多个CPU时,上锁进程可以等待一段T时间后,再次尝试上锁操作。Ngnix对T的构造有其独特的方法。

    • 上锁失败,放弃使用CPU

void ngx_shmtx_lock(ngx_shmtx_t *mtx)//自旋锁
{
 ngx_uint_t         i, n;  //初始化变量
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx lock");
for(;;){//不断循环进行
     自旋方式加锁;
      #if (NGX_HAVE_POSIX_SEM)
      信号量方式加锁,wait记录等待共享进程总数,等待进程挂入sem等待队列;
      #endif
      ngx_sched_yield();//优化方式放弃CPU
     }
}


//自旋式加锁
for ( ;; ) {   
      if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) 
       {           return;     }     //成功上锁返回  
      if (ngx_ncpu > 1) { //当有多个CPU时,等待T时间后,再次尝试上锁  
           for (n = 1; n < mtx->spin; n <<= 1){//构造等待时间T,再多次尝试上锁
               {              
                     for (i = 0; i < n; i++) { //每次都有等待时间T ,每次内循环等待次数不一样
                       ngx_cpu_pause();    //(借用CPU机制)优化自旋等待
              }           
             if (*mtx->lock == 0    && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid))      
              {                    return;                }  //再次尝试上锁若成功,则返回
      } 
   } 
ngx_sched_yield();//(优化)上锁失败,放弃使用CPU 。调度选中后,再次自旋上锁(为啥?)。
}
  • 信号量处理锁的获取
    如果是信号量:
    • 等待共享内存进程总数(预先)(原子性操作)加一;
    • 当lock为0(表示空闲)时可以上锁。按照nginx标准化上锁操作,也就是将lock(当其为0时)设为进程的PID。即
      *mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)。
      如果成功,将等待共享内存进程数减一(因已成功上锁,预计加需扣除),返回。
 (void) ngx_atomic_fetch_add(mtx->wait, 1);   //原子操作预加一
         if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid))
        {      //上锁成功了        
             (void) ngx_atomic_fetch_add(mtx->wait, -1); //原子操作减一        
             return;    //返回
        }
  • 如果上锁失败,将(该加锁进程)挂入sem的等待队列中。由于挂入sem的等待队列操作可能失败,为了确保1)中的加一操作与实际等待进程总数一致性,需要不断尝试挂入等待队列操作,直至成功挂入为止。否则数据将不一致。挂入等待队列的某进程,由释放锁某进程唤醒。
while (sem_wait(&mtx->sem) == -1) {//如果失败,再次进行挂入sem等待队列操作 
            ngx_err_t  err;               
            err = ngx_errno;     //获取错误原因   
           if (err != NGX_EINTR) { //若是系统原因,进行错误日志处理后,终止尝试      
                ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,   
                                "sem_wait() failed while waiting on shmtx");
                break;       
         }      
      }        
      ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx awoke");      
      continue; //进行下一个循环   
释放锁
void ngx_shmtx_unlock(ngx_shmtx_t *mtx);//释放锁
{   
 if (mtx->spin != (ngx_uint_t) -1) //调试信息处理
      {        ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
              "shmtx unlock"); 
       }  
  if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {//将lock设为0就是释放
        ngx_shmtx_wakeup(mtx);   //唤醒等待进程
 }
}
强迫解锁
ngx_uint_tngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid)//强迫解锁
{   
      ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, 
                  “shmtx forced unlock”); //记录调试信息
      if (ngx_atomic_cmp_set(mtx->lock, pid, 0)) { //共享内存lock为0(空闲)    
           ngx_shmtx_wakeup(mtx);    //唤醒等待共享进程进程   
           return 1;   
       }  
       return 0;//强制失败,返回0;
}
唤醒等待进程
  • 如果有信号量支持://因为只有有信号量支持时,才有sem等待队列
    • 如果没有标记使用信号量,(没有构造等待队列)返回。
#if (NGX_HAVE_POSIX_SEM)//由信号量支持
if (!mtx->semaphore) 
   {       
        return;  
    }
………//剩下的其它操作实现
#endif
  • 不断使用ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)对mtx->wait减一操作,直至成功将mtx->wait(原子操作)减一。
for ( ;; ) {  //不断尝试进行以下方式原子操作减一
    wait = *mtx->wait;     
    if ((ngx_atomic_int_t) wait <= 0) 
       {            return;        }    //没有等待共享内存进程,返回。
    if (ngx_atomic_cmp_set(mtx->wait, wait, wait - 1))    
          {          
                 break;  //成功原子操作减一,终止尝试原子减一。     
          }   
 }
  • 从sem等待队列中唤醒一个进程;
if (sem_post(&mtx->sem) == -1) 
{   //失败唤醒一个进程,错误日志处理
         ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,                      "sem_post() failed while wake shmtx");  
  }

slab共享内存块管理

nginx设计与实现了一种基于slab理念的共享内存块机制,并提供了创建共享内存块、从共享内存块中申请与释放内存的API。其结构体包括(core/ngx_slab.h以及core/ngx_slab.c):

  • ngx_slab_page_s:内存块管理结构体
    ngx_slab_stat_t:内存页使用信息管理结构体
    ngx_slab_pool_t:共享内存块结构体
    typedef struct ngx_slab_page_s ngx_slab_page_t;

nginx的slab大小规格

在这里插入图片描述

内存池结构体

typedef struct ngx_slab_page_s  ngx_slab_page_t;
struct ngx_slab_page_s 
{   
	uintptr_t         slab;   
	ngx_slab_page_t  *next; //后向
	uintptr_t         prev;//前向
};

typedef struct {   
	ngx_uint_t        total;  //总数  
	ngx_uint_t        used;   //使用总数 
	ngx_uint_t        reqs;  //请求总数 
	ngx_uint_t        fails;//失败总数
} ngx_slab_stat_t;

// 共享内存池结构体
typedef struct {    
	ngx_shmtx_sh_t    lock;  //内存锁 
	size_t            min_size;  //可以分配最小内存大小,即为8
	size_t            min_shift;  //最小slab内存的幂数,即min_size=2^ min_shift
	ngx_slab_page_t  *pages;  //指向第一页的管理结构
	ngx_slab_page_t  *last;   //指向最后页的管理结构
	ngx_slab_page_t   free;   //指向空闲首页的一个结点
	ngx_slab_stat_t  *stats;    //指向记录各种规格slab统计信息链表
	ngx_uint_t        pfree;   //空闲总页数
	u_char           *start;   //空闲页始址
	u_char           *end;//空闲末址
	ngx_shmtx_t       mutex;   //进程操作锁结构
	u_char           *log_ctx;  
	u_char            zero;   
	unsigned          log_nomem:1;  
	void             *data;   
	void             *addr;//共享内存池结构地址
} ngx_slab_pool_t;

在这里插入图片描述

共享内存池结构体slots

1.初始化共享内存池管理结构体各数据成员的值,理清控制管理关系。
2.分出控制管理结构后,剩余的即为可以共享分配的内存池。

管理不同规格的ngx_slab_page_t的首地址,nginx用宏ngx_slab_slots(pool)描述了这一大小位置关系:

在这里插入图片描述
(按情形)初始化共享池为0xA5(除共享池管理结构外)
在这里插入图片描述
初始化管理不同大小slab的ngx_slab_page_t
在这里插入图片描述
(按情形)初始化ngx_slab_stat_t
在这里插入图片描述
计算总页数pages

在这里插入图片描述
初始化pool的pages

在这里插入图片描述
初始化pool的free
在这里插入图片描述
初始化管理空页的首个ngx_slab_page_t
在这里插入图片描述
初始化pool的start

在这里插入图片描述
初始化pool的start,因对齐,修正总空闲数

在这里插入图片描述
初始化pool的其它成员

在这里插入图片描述

分配共享内存池
  1. 理论上,每个大小为KB的系统物理页,可以包含k/m个大小为mB规格的slab块。
  2. 为了标明一个系统物理页中含有的大小为mB规格slab块的占有情况,Nginx为每个系统物理页使用bitmap描述其含有的每个slab块是否空闲。
  3. 这样,每个大小为KB的系统物理页,需要k/m位描述其每个slab的空闲占有情况,如位1表示占有,如位0表示空闲。
  4. 对于小块内存(大小8Byte~32Byte),需要较多位(512b~128b)。nginx在内存首页开辟固定区域,码放这些bitmap。
  5. 对于精确内存(大小为64Byte),需要64b。nginx使管理内存页的ngx_slab_page_t结构体的slab字段作为bitmap。
  6. 对于大块内存(大小128Byte~2048Byte),需要(32b~2b)使用slab的前32位作为bitmap
void* ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size)
{
	void  *p;  
	ngx_shmtx_lock(&pool->mutex);//互斥分配
	p = ngx_slab_alloc_locked(pool, size); 
	ngx_shmtx_unlock(&pool->mutex); //互斥分配
	return p;
}

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

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

相关文章

ctfshow-web入门命令执行29

29 源代码给了禁用flag 使用tac、nl ?cecho nl f*; ?cecho tac f*; 30 多禁用了system和php 和上题区别不大&#xff0c;使用上一题命令就能解 ?cecho nl f*; ?cecho tac f*; 31 禁用了空格使用%09代替 ?cecho%09tac%09f*; 32 禁用了echo 使用php伪协议 ?cinclud…

不做学习的奴隶,更要注重生活

下面是国外社交软件 i n s ins ins上近 40 40 40万点赞的帖子。 “睡8小时&#xff0c;而不是6小时。 锻炼1小时&#xff0c;而不是4小时。 学习3小时&#xff0c;而不是10小时。 读书2小时&#xff0c;而不是5小时。 深度工作3小时&#xff0c;而不是12小时。 你是人&#xff…

uniapp 模仿 Android的Menu菜单栏

下面这张图就是我们要模拟的菜单功能 一、模拟的逻辑 1. 我们使用uni-popup组件&#xff08;记得要用hbuilder X导入该组件&#xff09;uni-app官网 2. 将组件内的菜单自定义样式 二、uniapp代码 写法vue3 <template><view><uni-popup ref"showMenu"…

设计师在团队协作中的关键角色与策略

作为设计师&#xff0c;团队协作也是日常工作的一部分。在设计团队中&#xff0c;设计师如何参与团队协作&#xff1f;怎样才能更好的发挥自己的价值&#xff0c;顺利推进项目呢&#xff1f; 设计师遇到的协作难题&#xff1f; 首先我们看一下设计师在日常团队协作工作中可能…

C语言实现输入一个字符串,递归将其逆序输出

完整代码&#xff1a; // 输入一个字符串&#xff0c;递归将其逆序输出。如输入 LIGHT&#xff0c;则输出 THGIL #include<stdio.h> #include<stdlib.h> //字符串的最大长度 #define N 20//逆序输出字符串 void func(char *str){if (*str\0){//结尾时直接退出递归…

常见网络攻击及防御方法总结(XSS、SQL注入、CSRF攻击)

网络攻击无时无刻不存在&#xff0c;其中XSS攻击和SQL注入攻击是网站应用攻击的最主要的两种手段&#xff0c;全球大约70%的网站应用攻击都来自XSS攻击和SQL注入攻击。此外&#xff0c;常用的网站应用攻击还包括CSRF、Session劫持等。 1、 XSS攻击 XSS攻击即跨站点脚本攻击&am…

VBA宏查找替换目录下所有Word文档中指定字符串

原来搞质量管理&#xff0c;要替换质量文件里面所有特定名称或者某一错误时&#xff0c;需要逐一打开所有文件&#xff0c;非常麻烦&#xff0c;所以写了个VBA程序。过了这么多年&#xff0c;突然又要做同样的事情&#xff0c;发现新版本Word不支持其中的Application.FileSearc…

python自动化测试(五):按键模拟输入:全选、复制、清空、粘贴、完成

前置条件&#xff1a; 本地部署&#xff1a;ECShop的版本是3.0.0、Google版本是 Google Chrome65.0.3325.162 (正式版本) &#xff08;32 位&#xff09; Google驱动的selenium版本是3.11.0 目录 一、配置代码 二、键盘组合输入 2.1 全选&#xff1a;ctrl a 2.2 复制…

2023上半年系统集成项目管理工程师下午真题

文章目录 一&#xff1a;第5章 项目立项管理。第7章 项目范围管理&#xff0c;需求文件二&#xff1a;第9章 项目成本管理。第8章 项目进度管理&#xff0c;压缩工期三&#xff1a;第15章 信息&#xff08;文档&#xff09;和配置管理四&#xff1a;第18章 项目风险管理&#x…

ELASTICO-A Secure Sharding Protocol For Open Blockchains

INTRO 在中本聪共识中&#xff0c;通过POW机制来公平的选举leader&#xff0c;不仅非常消耗power&#xff0c;并且拓展性也不好。现在比特币中是7 TPS&#xff0c;和其他的支付系统相比效率相差甚远。 当前的许多拜占庭共识协议&#xff0c;并不支持在一个开放的环境中使用&a…

Linux 音频驱动实验

目录 音频接口简介为何需要音频编解码芯片&#xff1f;WM8960 简介I2S 总线接口I.MX6ULL SAI 简介 硬件原理图分析音频驱动使能修改设备树使能内核的WM8960 驱动alsa-lib 移植alsa-utils 移植 声卡设置与测试amixer 使用方法音乐播放测试MIC 录音测试LINE IN 录音测试 开机自动…

探索低代码PaaS平台的优势与选择原因

PaaS是一种云产品&#xff0c;它为应用程序的开发和部署提供基础结构。它提供中间件、开发工具和人工智能来创建功能强大的应用程序&#xff0c;大多数PaaS服务都与存储和网络基础架构捆绑在一起&#xff0c;就像基础架构即服务&#xff08;IaaS&#xff09;一样&#xff0c;可…

在Spring boot中 使用JWT和过滤器实现登录认证

在Spring boot中 使用JWT和过滤器实现登录认证 一、登录获得JWT 在navicat中运行如下sql,准备一张user表 -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS t_user; CREATE TABLE t_user (id int(11) …

css文字竖向排列

div { writing-mode: vertical-rl;text-orientation: upright;font-size: .25rem; //文字大小letter-spacing: 0.1em; //文字间距}

Ubuntu安装ddns-go使用阿里ddns解析ipv6

Ubuntu安装ddns-go 1.何为ddns-go2.安装环境3.获取ddns-go安装包4.解压ddns-go5.安装ddns-go6.配置ddns-go 1.何为ddns-go DDNS-GO是简单好用的DDNS&#xff0c;它可以帮助你自动更新域名解析到公网IP。比如你希望在本地部署网站&#xff0c;但是因为公网IP是动态的&#xff0…

Transformer模型 | Python实现Attention-Transformer时间序列预测

时间序列预测 | Python实现Attention-Transformer时间序列预测 目录 时间序列预测 | Python实现Attention-Transformer时间序列预测基本介绍模型结构程序设计学习总结基本介绍 Python实现Attention-Transformer时间序列预测(TSAT model) 模型结构 main.py :含训练集合测试集的…

【图像分类】卷积神经网络之ResNet网络模型实现钢轨缺陷识别(附代码和数据集,PyTorch框架)

写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 本篇博文,我们将使用PyTorch深度学习框架搭建ResNet实现钢轨缺陷识别,附完整的项目代码和数据集,可以说是全网…

力扣:144. 二叉树的前序遍历(Python3)

题目&#xff1a; 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 示例&#xff1a; 示例 1&#xff1a; 输…

spring框架回顾

如果是web项目,spring也推荐使用jar包而不是war包,因为如果是web项目,在下一页可以选择spring web,它里面就包含了spring mvc ,底层会有一个嵌入式的tomcat,到时候直接执行就可以了 正因为有此功能,所以把模块打成jar包后,可以直接java -jar [jar包名] 运行,直接可以在电脑访问…

dc-5 靶机

1.扫描ip地址 2.网页 3.dirb 爆破目录 没有用 4.爆破端口 没有用 5. 文件上传漏洞 上传点 写一句话木马 蚁剑连接 1.shell反弹 蚁剑反弹 提权 使用命令 命令"find / -perm -us -type f 2>/dev/null"在整个文件系统 ("/") 中搜索设置了SUID权…