Linux知识点

Linux

1.常见指令与权限

1.1指令

1.ls:列出当前目录下的所有文件

-a包括隐藏文件

2.pwd:显示当前所在路径;

3.cd:去指定的路径,作为工作路径;

4.touch:在当前目录下新建一个文件;

5.makdir:在当前位置新建一个目录;

6.rmdir/rm:rmdir与mkdir相对,删除指定目录,rm删除目录或文件

-f只读的文件一样删除;

-r删除所有目录与文件;

-i删除前询问

7.man:查询文档;

**8.cp **复制文件cp [选项] 源文件或目录 目标文件或目录

-r递归复制

9.mv:移动文件 mv [选项] 源文件或目录 目标文件或目录

1.2权限

1.2.1用户权限

Linux下有超级用户和普通用户俩种

切换 su命令;

1.2.2文件权限与文件类型

文件权限:rwx分别为可读可写可执行 二进制表示rwx为111十进制即为7;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kt9Ky4DL-1598114885180)(C:\Users\wanghao\Desktop\面试\知识点\截图\TIM截图20200815230746.png)]

文件权限 : 文件所有者权限 : 文件所有组权限 : 其他用户权限

文件类型

d:文件夹

-:普通文件

l:软链接(类似Windows的快捷方式)

b:块设备文件(例如硬盘、光驱等)

p:管道文件

c:字符设备文件(例如屏幕等串口设备)

s:套接口文件

文件权限的设置
chmod 权限 文件名

2.开发工具

2.1软件管理工具yum

yum:是一个Linux下的软件包管理工具,在安装时需要查看网络是否连接使用ping;

安装软件

yum install 软件

卸载软件

yum remove 软件

2.2编辑器vim

vim:是一款Linux下的编辑器,

vim的使用

vim / vi 文件名 进入编辑模式

进入默认为正常模式,按a可以调整为插入模式,esc重新回到默认正常模式,正常模式下退出加:输入指令;

wq 保存退出;

w保存;

q!强制退出;

3.编译器gcc/g++

背景知识代码转化为可执行程序的过程

**预处理:**进行宏替换,去注释,展开头文件,条件编译;

**编译:**检查语法,将代码转化为汇编代码;

**汇编:**将汇编代码转化为二进制机器码;

链接:将文件链接到一起形成可执行程序;


函数库:分为静态库与动态库,静态库是直接将代码套入,动态库不然,是链接文件到程序;

gcc操作: gcc [选项] 要编译的文件 [选项] [目标文件];

gcc选项

-o文件输出到目标文件

示例

gcc test.c -o test//将test.c编译为test可执行程序
./test //运行程序
4.项目自动化构建工具make/makefile

make:是针对makefile的一种指令

makefile:编写示例:

main : main.c
    gcc main.c -o main

3.进程

3.1操作系统

3.1.1操作系统
  1. 操作系统存在的意义是为计算机提供一个软硬件交互的工具;

  2. 操作系统包括 内核(进程管理、内存管理、文件管理、驱动管理) ,其他程序

3.1.2系统调用与库函数

系统调用:操作系统对外的接口,供上层开发使用,这部分系统提供的接口,叫系统调用;

库函数:开发者对系统接口进行封装,形成了库,库的存在有利于二次开发;

3.2进程

基本概念:进程信息放在一个叫进程控制块PCB中,Linux下的PCB是一个task_struct

3.2.1创建进程fork函数

fork函数有俩个返回值,即父子进程,== 0创建子进程,>0为父进程模块,<0创建失败;

父子进程代码共享,数据各自开辟空间,并且私有(采用写时拷贝技术)

3.2.2进程状态

1.R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。

2.S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。

3.D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。

4.T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。

5.X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

6.Z僵尸进程:子进程先于父进程退出,且父进程没有读取到子进程的退出代码,子进程进入僵尸进程;

父进程收不到子进程的退出句柄,则子进程一直处于Z状态;

若子进程一直不退出,pcb一直需要给子进程提供维护,浪费资源;

会造成内存泄漏

避免–进程等待

孤儿进程:父进程先于子进程退出,子进程无人收养,被1号进程init进程收养,子进程被称为孤儿进程

4.进程地址空间

父子进程的地址相同,对应的是物理地址吗?

变量内容不一样,所以父子进程输出的变量绝对不是同一个变量 但地址值是一样的,说明,该地址绝对不是物理地址!

在Linux地址下,这种地址叫做 虚拟地址

我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理

4.1分页存储&&虚拟地址空间

父子进程的内存地址相同但是内容并不一样,是因为使用了页表映射

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8nY3kqTs-1598114885186)(C:\Users\wanghao\Desktop\面试\知识点\截图\TIM截图20200817135428.png)]

**分段式:**地址:段号+段内偏移量 、 段表: 段表+物理起始地址 、将地址空间分为数据段,代码段,方便编译器的地址管理;

分页式:

虚拟地址找到物理地址:
虚拟地址= 页号+页内偏移;
物理地址=块号(由虚拟地址映射可得)*页面大小 +页内偏移量;

段页式:将地址分段,再分页管理;

缺页中断:访问时发现该地址中的数据不存在;

磁盘交换分区:作为交换内存使用,物理内存不够时,按照内存置换算法将物理内存中的某些数据移动到交换分区中保存,腾出内存加载新的数据进行处理;

4.2写时拷贝

写时拷贝:当父子进程不再写入时,代码也是共享的,当一方进行写入时,另一方会存一份副本;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Me5gAjAr-1598114885189)(C:\Users\wanghao\Desktop\面试\知识点\截图\写时拷贝.png)]

不再写入时,代码共享 写入时,各存一份副本

4.3进程退出

正常终止(可以通过 echo $? 查看进程退出码):

  1. 从main返回
  2. 调用exit
  3. _exit

异常退出:

  1. ctrl + c,信号终止
4.4进程等待

在子进程先于父进程退出,父进程没有接收到子进程退出句柄,会造成僵尸进程,父进程的进程等待可以避免这个问题,避免了内存泄漏;

父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息;

4.4.1进程等待的方法

1.wait方法

pid_t wait(int*status);

返回值:

成功返回被等待进程pid,失败返回-1

参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

2.waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);

返回值

  1. 当正常返回的时候waitpid返回收集到的子进程的进程ID;

  2. 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

  3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;


注意

  1. 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退 出信息。
  2. 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  3. 如果不存在该子进程,则立即出错返回。
4.5进程替换

进程替换
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

进程替换

5.IO

5.1标准库的IO

fopen:打开文件

fclose:关闭文件

fread:读取文件

fwrite:写入文件

r / r+ 、w / w+ a/a+ b

r只读 w只写;

r+读写 w+读写;

a追加写,a+追加读写;

b按二进制方式读取;

注意事项:文本操作与二进制操作的区别

文本操作是按字符进行读取;

二进制更加精确;

5.2系统调用IO

open、read、write、lseek、close的使用

与c语言不同的是,一个是库函数,一个是系统调用接口

5.3文件描述符

定义:文件描述符是一个非负整数,实质上是内核PCB进程打开文件文件信息描述表的数组下标;

操作:通过文件操作描述符操作文件,就是拿着描述符在内核中找到pcb中的IO信息表的数组表;

重定向:针对描述符的重定向,让描述符执行下一个文件的描述信息;

描述符与文件流指针的关系:文件流指针是FILE结构体,结构体中包含文件描述符成员;

5.4磁盘文件管理

区域:超级块、inode位图、数据块位图、inode表、数据块

文件数据存储:通过文件描述符进入超级块,找到俩个位图,寻找空闲区域进行数据存储;

文件数据获取:通过文件名找到inode节点号,通过节点号找到inode表区域,再找到数据块,取出数据;

软链接与硬链接

本质

​ 软链接文件是一个独立的文件,有自己的inode,数据保存的是源文件路径;

​ 硬链接文件与源文件相同是一个文件目录项,共享一个inode;

区别

  1. 软连接针对目录可以创建,硬链接不可以;
  2. 软链接可以跨分区建立,硬链接不可以;
  3. 删除源文件,软链接文件失效,硬链接可以用;

6.进程间通信

进行间通信的必要性:进程间互相独立,每个进程访问的都是自己的虚拟地址,无法进行通讯;

6.1管道

6.1.1 本质

管道的本质是内核中的缓冲区

6.1.2管道的种类

匿名管道

1.缓冲区没有标识符,只能通过子进程复制父进程的方式取到管道的操作句柄;

2.只能用于具有亲缘关系的进程之间的通讯;

命名管道

1.缓冲区具有标识符,可见于文件系统的管道文件,通过打开相同的管道文件访问相同缓冲区;

2.可以用于同一主机的任意进程间通信;

6.1.3管道的特性
  1. 半双工通信
  2. 生命周期跟随进程生命周期
  3. 提供字节流服务 :(字节流是先进先出以字节为单位的连接传输)
  4. 自带同步互斥;

​ **同步:**管道没有数据写操作阻塞,数据满了读操作阻塞;

互斥:对临界资源的访问具有唯一性,写入大小不超过PIPE_BUF-4096字节大小保证原子性;

6.1.4代码操作

pipe/mkfifo相关操作


6.2消息队列

6.2.1本质

内核中的一个优先级队列,多个进程通过向同一队列添加/获取节点实现通讯

6.2.2特性

自带同步与互斥

生命周期伴随内核的周期

6.3共享内存

6.3.1本质

一块物理内存,多个进程通过映射同一块物理内存到各自的虚拟地址空间进行访问

6.3.2特性
  1. 最快的一种进程间通信
  2. 较其他通讯方式少了用户态与内核态和数据拷贝
  3. 生命周期随内核
  4. 需要注意保护,自身不存在同步与互斥
6.3.3代码实现及命令

命令操作

ipcs -m 查看信息

ipcrm -m 删除信息

6.4信号量

本质:内核中的计数器

生产者消费者模型

读者写者模板

7.进程信号

7.1信号概念

信号:信号是一个软件中断,通知进程发生了某个事件,打断进程的当前操作,去处理事件;

信号的种类

1-31 非可靠信号;

34-64可靠信号;

7.2信号的生命周期

7.2.1信号的产生

硬件:ctrl + c/z/l

软件:kill() / raise() / abort() / alarm() / sigqueue()

7.2.2信号在进程的注册

注册:在进程的pcb中的pending集合中标记信号的到来,以及在sigqueue链表中添加信号节点;

可靠信号的注册:信号若已经注册则不再注册;

非可靠信号的注册:不管信号是否注册,都会注册一个新的信号;

7.2.3信号的处理

处理即执行信号的处理函数

代码操作:signal

7.2.4信号的阻塞

信号来了之后,注册后也不会马上进行处理;

实现:在内核block集合中标记信号,哪些信号被标记,则这些信号暂时不会处理;

代码操作:sigprocmask;

sigempytset/sigfillset/sigaddset/sigismember/sigdelset

8.多线程

8.1线程概念

定义:线程是进程的一个执行流,在Linux下通过PCB实现,同一进程可以有多个线程,这些线程共享进程的资源,因此有时被称为轻量级进程;

Linux下进程与线程的边界

  1. 进程是资源分配的基本单位;
  2. 线程是cpu调度的基本单位;

线程的独有与共享

  1. **独有:**栈、寄存器、优先级、信号屏蔽字、标识符;
  2. 共享:虚拟地址空间、信号处理方式、IO信息、工作路径;

多进程与多线程的多任务处理

共同点:

多执行流进行多任务并发执行/并发处理的处理效率高;

不同点

多线程:

  1. 线程通信更加灵活(包括进程间通信方式外还有全局数据)
  2. 线程的创建和销毁成本更低;
  3. 同一进程间的线程调度成本更低;
  4. 一些系统调用以及异常会对整个进程产生效果;

多进程:

稳定,进程间相互独立,应用于主程序功能稳定运行的场景;

8.2线程控制

8.2.1创建

进行线程控制的接口,都是库函数;

接口解析

int pthread_creat(pthread_t *thread, const pthread_addr_t *arr , void *(*start_routine(void*),void* arg));

参数解析

  1. thread:输出型参数,用于获取线程id–线程的操作句柄
  2. attr:线程属性,用于在创建线程时设置线程属性,通常置空;
  3. start_routine函数指针,是一个线程的入口函数–线程运行的就是这个函数,函数运行完毕,线程退出;
  4. arg:通过线程的入口函数,传递给线程的参数;
  5. 返回值:成功返回0,失败返回错误编号;

tid是一个无符号的长整型数据;

一个线程有唯一的pcb,每一个pcb有一个pid – pid是一个整型数据;

tid与pid的联系:

在这里插入图片描述

tid是一个用户态线程的操作句柄,是用户态线程的id,物理层面上实际上是线程这块地址独有的首地址;

pid是一个轻量级进程id,内核中task_struct结构体中的id;

task_struct->pid:轻量级进程id,即ps -elf显示的LWP

task_struct->tgid:线程组id,等于主线程id,即外部的进程id;

8.2.2终止

线程终止:线程入口函数运行完毕,线程会自动退出,在线程入口函数中调用return(但是main函数中的reutrn,退出的是进程而非主线程);

接口介绍

pthread_exit

void pthread_exit(void* retval);
  1. 退出线程接口,谁调用谁退出,retval是退出的返回值;
  2. 主线程退出,并不会导致进程退出,只有所有的线程都退出,进程才会退出;

pthread_cancel

int pthread_cancel(pthread_t thread);

终止一个线程,退出的线程是被动取消的;

8.2.3等待

线程等待:等待一个线程的退出,获取退出线程的返回值,回收线程所占资源;

线程·有一个属性,默认创建出来的这个属性是jionable,处于这个属性的线程,退出后,需要被其他线程等待获取返回值回收资源;

pthread_join:

int pthread_join(pthread_t thread,void **retval);

参数介绍:

thread:需要等待线程tid;

retval:输出型参数,线程的返回值;

线程的返回值是一个void* ,是一个一级指针,若通过一个函数的参数,需要传地址;

默认情况下:一个线程必须被等待,不等待会造成资源泄漏;

8.2.4分离

线程分离:将线程jionable修改为detach属性;

一个线程属性若是jionable那么必须被等待;

一个线程属性若是detach那么这个线程退出后则自动释放资源,不需要被等待(因为资源已经释放)

分离一个线程,一定是不想获取线程的返回值,又不想等待,才会出现分离线程;

int pthread_detach(pthread_t thread);

将线程属性修改为detach

pthread_t pthread_self(void);//返回线程pid

8.3线程安全

8.3.1概念

线程安全:多个执行流同时对临界资源进行争抢访问不会造成数据二义性;

8.3.2互斥

互斥:同一时间只有一个执行流可以访问资源,保证资源访问的安全性;

互斥锁:只有0/1的计数器,用于标记资源的访问状态,在访问临界资源之前先访问互斥锁判断是否允许访问,不允许则使执行流阻塞,允许则资源设置为不可访问,资源访问完毕则改为可访问状态;

示例: A进程和B进程一起争取临界资源BUF,只允许一个进程访问,访问标记为1,释放标记0;

代码操作

1.定义互斥锁变量

pthread_mutex_t

2.初始化互斥锁

pthread_mutex_init();

3.访问临界资源前加锁

pthread_mutex_lock();

4.访问临界资源后解锁

pthread_mutex_unlock();

5.销毁互斥锁

pthread_mutex_destroy();

注意事项

  1. 互斥锁需在创建执行流之前初始化;
  2. 在任何有可能退出执行流的地方解锁;
8.3.3死锁

概念:描述执行流阻塞无法推进的状态;

产生:多执行流访问多个有锁资源,但是由于推进顺序不对,导致无法继续;

产生死锁的必要条件

  1. 互斥条件;(执行流之间存在互斥条件)
  2. 环路等待条件;(执行流自身没有解锁的情况下,对方在请求该执行流的资源,对方也没有解锁,一直在环路等待)
  3. 不可剥夺条件;(资源在没有使用完之前,不允许被剥夺)
  4. 请求与保持条件;(执行流在拥有资源的前提下,可以继续请求已经被占用的资源)

预防:预防其产生的必要条件;

避免:银行家算法


8.3.4同步

同步:通过条件判断,不能获取资源的时候执行流阻塞,保证资源获取的合理性;

条件变量

  1. 线程在满足资源的访问条件的时候才可以访问资源,否则就挂起线程,直到满足条件后再唤醒线程;
  2. 条件变量向外提供了一个使线程等待的接口和唤醒线程的接口+pcb等待队列;
  3. 条件变量只提供等待和唤醒的接口,什么时候进行操作需要用户自己在进程中判断;

操作代码

1.定义条件变量

pthread_cond_t

2.初始化条件变量

pthread_cond_init(pthread_cond_t *,pthread_condattr_t*)

3.使线程挂起休眠的接口

条件变量需要配合互斥锁一起使用(判断条件本就是一个临界资源)

pthread_cond_wait(pthread_cond_t *,pthread_condattr_t*)//等待被请求资源唤醒
pthread_cond_timedwait(pthread_cond_t *,pthread_mutex_t*,struct timespec);//指定时间唤醒

4.唤醒线程的接口

pthread_cond_signal(pthread_cond_t*)//唤醒至少一个线程
pthread_cond_broadcast(pthread_cond_t*)//唤醒所有线程

5.销毁条件变量

pthread_cond_destory(pthread_cond_t*);

注意事项

  1. 在判断时需要使用循环判断;
  2. 多种不同的角色执行流应该使用多个条件变量;
8.3.5信号量

信号量

  1. 可以用于实现进程/线程之间的同步/互斥;
  2. 信号量的本质是一个计数器+pcb等待队列;

同步

  1. 通过自身的计数器对资源进行计数,通过计数器资源的个数,判断进程/线程是否可以达到访问资源的条件;
  2. 若符合就可以访问,若不符合则调用提供的接口使进程/线程阻塞,其他进程/线程条件满足时,可以唤醒pcb等待队列上面的pcb;

互斥

保证计数器的值不超过1,就可以保证资源的唯一性,同一时间,进程/线程访问资源,产生互斥;

代码操作

1.定义信号量

sem_t

2.初始化信号量

int sem_init(sem_t* sem, int pshared, unsigned int value);

参数解析

  1. sem:信号量变量;
  2. pshared:0/1 、0用于线程间,非0用于进程间;
  3. value:初始化信号量的初值,初始化计数器上的资源数量;
  4. 返回值:成功返回0,失败返回-1

3.访问临界资源

访问临界资源之前,访问信号量,如能够访问,计数器-1;

int sem_wait(sem_t* sem);
//通过计数条件判断是否可以访问,不满足则阻塞进程/线程
int sem_trywait(sem_t* sem);
//判断后不满足,则报错返回;
int sem_timedwait(sem_t* sem,const struct timespce* abs_timeout);
//不满足则等待指定时间,超时后返回报错;

4.离开临界资源

离开临界资源,计数器+1,唤醒阻塞进程/线程

int sem_post(sem_t* sem);
//通过信号量唤醒自己阻塞队列上的pcb;

5.销毁信号量

int sem_destory(sem_t* sem);
8.3.6生产者消费者模型:

应用场景:一个进程即需要产生数据,又需要处理数据的场景;

思想:一个执行流产生数据,一个执行流处理数据,执行流之间通过数据缓冲队列进行交互;

优点:解耦合,忙线不均,支持并发;

实现:实现一个线程安全的数据队列,实现两个角色线程;

使用数组实现生产者消费者模型

  1. 需要满足先进先出的队列特性
  2. read == write时。表示队列中没有数据;
  3. 写入数据后,write指针++;
//结构设计
class RingQueue
{
    std::vector<int> _queue;
    int _capactiy;
    int _step_read;
    int _step_write;
    
    sem_t_lock;//实现互斥
    sem_t_sem_idle;
    //对空闲空间进行计数,空间>0才可以写入数据;
    sem_t_data;
    //对具有数据的空间计数,数据0才可以读取数据;
}
8.3.7读者写者模型:

应用场景:读共享,写互斥;

如多个执行流下的文件读写,可以一起读数据,但是只允许一个执行流写数据;

实现

读写锁:

  1. 读者计数器,写者计数器;
  2. 自旋锁,条件不满足,循环判断;

8.4线程应用

8.4.1线程池

思想

  1. 线程的一个池子,有很多的线程,但是不会超过池子的限制;
  2. 需要用到多执行进行任务处理时,就从线程池中取线程处理;

应用场景

有大量的数据处理请求,需要执行流并发/并行处理;

风险分析

若一个数据请求创建一个线程,会产生风险和不必要的消耗

  1. 线程若不限制数量的创建,在峰值压力下,线程创建过多,资源耗尽,程序有崩溃风险;
  2. 处理一个任务的时间,创建线程时间T1+任务处理时间T2+线程销毁时间T3 == T,若大量时间浪费在创建与销毁,资源会浪费,效率不高,线程池里面的线程循环任务处理,避免了这些资源损耗;

编写一个线程池

线程池组成 == 大量线程+任务缓冲队列

传统线程入口函数,处理任务的方式与处理流程过于单一,灵活性不强;

线程池示意图

在这里插入图片描述

线程池中的线程只需要传入方法于需要处理的数据即可,不需要关心如何处理;

typedef void(*_handler)(int data);
class MyTask
{
    private:
     int data;//需要处理的数据
     handler_t handler;//需要的方法
    public:
     SetTask(int data,handler_t handler);
    //用户传入数据,组织出一个任务节点
     Run()
     {
         return _handler(_data);
     }
}

class ThreadPool
{
    int thr_max;//定义线程的最大数量
    std::queue<Mytask> _queque;//任务处理队列
    pthraed_mutex_t _mutex;//实现任务队列的操作安全
    pthread_cond_t _cond;//实现线程池中的线程同步
}

需要处理什么数据,什么方法,组织一个任务节点,传给线程池,线程池进行处理

8.4.2线程安全的单例模式

单例模式

一份资源只能被加载一次/单例模式发的方法创建的类只能被实例化一次;

实现

饿汉模式:

  1. 资源初始化的时候就去加载,后面可以之间使用,以空间换时间;
  2. 使用的时候比较流畅,有可能会加载用不上的资源,并且会导致初始化的时间较慢;
  3. 使用static–将成员变量设置为静态变量,所有对象共用一份资源,程序初始化时就会申请资源,不涉及线程安全;

懒汉模式

程序初始化比较快,第一次运行某个模块可能比较慢,因为需要加载需要的资源;

使用细节:

  1. 使用static保证所有对象使用同一份资源;
  2. 使用volatile,防止过度优化;
  3. 实现线程安全,保证资源的判断和申请过程是安全;
  4. 外部二次判断,避免每次成功获取都需要加锁解锁,以及避免死锁;

问题

STL容器都是线程安全吗 – 不是;见C++11

智能指针是线程安全的吗 ,unipue_ptr 是局部操作 / shared_ptr是原子操作,不涉及线程安全问题;

完结…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值