同步消息和异步消息,Linux C中多线程与volatile变量,mdadm工具,gettid和pthread_self区别,dlopen、dlsym、dlclose使用动态加载库,sysconf函数

一、同步消息和异步消息传递的区别?

(1)同步与异步消息的区别

  • 同步消息
    同步消息传递涉及到等待服务器响应消息的客户端。消息可以双向地向两个方向流动。本质上,这意味着同步消息传递是双向通信。 即发送方向接收方发送消息,接收方接收此消息并回复发送方。发送者在收到接收者的回复之前不会发送另一条消息。
    同步执行的特征为:在两个通信应用系统之间必须要进行同步, 两个系统必须都在正常运行, 并且会中断客户端的执行流, 转而执行调用。发送程序和接收程序都必须一直做好相互通信的准备。发送程序首先向接收程序发起一个请求(发送消息)。发送程序紧接着就会堵塞它自身的进程, 直到收到接收程序的响应。 发送程序在收到响应后会继续向下进行处理。

  • 异步消息
    异步消息传递涉及不等待来自服务器的消息的客户端。事件用于从服务器触发消息。因此,即使客户机被关闭,消息传递也将成功完成。异步消息传递意味着,它是单向通信的一种方式,而交流的流程是单向的。
    当使用异步消息传送时, 调用者在发送消息以后可以不用等待响应, 可以接着处理其他任务。对于异步通信, 一个应用程序(请求者或发送者)将请求发送给另一个应用程序, 然后可以继续向下执行它自身的其他任务。发送程序无须等待接收程序的执行和返回结果, 而是可以继续处理其他请求。与同步方式不同, 异步方式中两个应用系统(发送程序和接收程序)无须同时都在运行, 也无须同时都在处理通信任务。

(2)同步和异步消息传递的优点和缺点

  • 异步消息传递有一些关键优势。它们能够提供灵活性并提供更高的可用性——系统对信息采取行动的压力较小,或者以某种方式立即做出响应。另外,一个系统被关闭不会影响另一个系统。例如,电子邮件——你可以发送数千封电子邮件给你的朋友,而不需要她回复你。

  • 异步的缺点是它们缺乏直接性。没有直接的相互作用。考虑一下与你的朋友在即时通讯或电话上聊天——除非你的朋友及时回复你,否则这不是聊天或谈话。

二、volatile变量

(1)Linux C中多线程与volatile变量

  • volatile 修饰的变量表示改变量的值是易变的,编译器不对其进行优化,访问该变量的时候不会从寄存器读取, 而是直接从内存读取变量。

  • 在多线程环境下,每个线程都有一个独立的寄存器,用于保存当前执行的指令。假设我们定义了一个全局变量,每个线程都会访问这个全局变量,这时候线程的寄存器可能会存储全量变量的当前值用于后续的访问。

  • 当某个线程修改了全局变量的值时,系统会立即更新该线程寄存器中对应的值,其他线程并不知道这个全局变量已经修改,可能还是从寄存器中获取这个变量的值,这个时候就会存在不一致的情况。

  • 针对多线程访问共享变量而且变量还会经常变化的情况,利用volatile类型修饰变量是一个很好的选择,如volatile int size = 10; 当多线程访问这个变量时,它会直接从size对应的地址访问,而不会从线程对应的寄存器访问,这样就不会出现前面说到的

  • 同一变量的值在多个线程之间不一致的情况。

  • eg如下:

#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
 
/* volatile变量控制线程的运行与结束 */ 
static volatile int do_run_thread = 1;  
 
static pthread_t thread_tid; 
 
static void *work_thread(void *arg) 
{ 
  while (do_run_thread) { 
    printf("thread is running...\n"); 
    sleep(1); 
  } 
  printf("stop thread done!\n"); 
} 
 
static void start_thread() 
{ 
  printf("start thread...\n"); 
  pthread_create(&thread_tid, NULL, work_thread, NULL); 
} 
 
static void stop_thread() 
{ 
  printf("stop thread...\n"); 
  do_run_thread = 0; 
  pthread_join(thread_tid, NULL); /* 等待线程结束 */ 
} 
 
int main() 
{ 
  start_thread(); 
  sleep(5); 
  stop_thread(); 
 
  return 0; 
} 

(2)linux C 中的volatile使用

  • 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
    1). 并行设备的硬件寄存器(如:状态寄存器)
    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
    3). 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。

  • 假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
    1). 一个参数既可以是const还可以是volatile吗?解释为什么。
    2). 一个指针可以是volatile 吗?解释为什么。
    3). 下面的函数有什么错误:
  int square(volatile int *ptr) 
     { 
          return *ptr * *ptr; 
     } 

  下面是答案: 
    1). 是的。一个例子是只读的状态寄存器。它是volatile:它可能被意想不到地改变。const:程序不应该试图去修改它。 
    2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。 
    3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码: 
    int square(volatile int *ptr)  
    { 
         int a,b; 
         a = *ptr; 
         b = *ptr; 
         return a * b; 
     } 
    由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下: 
     long square(volatile int *ptr)  
     { 
            int a; 
            a = *ptr; 
            return a * a; 
     }

三、mdadm工具–搭建软RAID

(1)Raid含义

  • RAID是廉价磁盘的冗余阵列,用于在大规模环境中,需要的数据比正常使用被保护的高可用性和可靠性。 Raid只是一个池中的磁盘集合,成为一个逻辑卷并包含一个数组。 组合驱动程序创建一个数组或称为一组(组)。

  • 可以创建RAID,如果有至少2个磁盘连接到RAID控制器,并且创建一个逻辑卷或更多的驱动器可以根据定义的RAID级别添加到阵列中。 软件Raid可以不使用物理硬件,这些被称为软件RAID。 软件Raid将被命名为Poor man raid。

  • 在Linux中设置RAID0
    使用RAID的主要概念是从单点故障保存数据,意味着如果我们使用单个磁盘存储数据,如果它失败,那么没有机会获得我们的数据,为了停止数据丢失我们需要一个容错方法。 所以,我们可以使用一些磁盘集合来形成RAID集。

其它:https://www.howtoing.com/understanding-raid-setup-in-linux/
在这里插入图片描述

(2)什么是RAID 0中的条纹?

Stripe通过划分内容在同一时间跨多个磁盘分割数据。 假设我们有两个磁盘,如果我们将内容保存到逻辑卷,它将通过划分内容保存在两个物理磁盘下。 为了获得更好的性能的RAID 0将被使用,但是,如果该驱动器的一个出现故障,我们不能得到的数据。 因此,使用RAID 0不是一个好的做法。唯一的解决方案是安装操作系统与RAID0应用逻辑卷,以保护您的重要文件。
RAID 0四个特点如下:

RAID 0具有高性能。
RAID 0中的容量损失为零。不会浪费空间。
零容错(如果任何一个磁盘出现故障,则无法恢复数据)。
写作和阅读将是极好的。

(3)要求

磁盘最小数目允许创建RAID 0,但你可以添加更多的磁盘,但顺序应该是两倍2,4,6,8,如果你有一个物理RAID卡有足够的端口,你可以添加更多磁盘。

这里我们不使用硬件RAID,此设置仅取决于软件RAID。 如果我们有一个物理硬件RAID卡,我们可以从它的效用UI访问它。 有些主板在默认情况下RAID功能在建,还有UI可以使用Ctrl + I键访问。

(4)安装mdadm来管理RAID

在这里这部分,我们将看看我们如何能够创建并使用名为SDB和SDC 2 20GB硬盘的Linux系统或服务器安装软件RAID0或分拆

  • 我的服务器设置
Operating System :	CentOS 6.5 Final
IP Address	 :	192.168.0.225
Two Disks	 :	20 GB each

第1步:更新系统并安装mdadm以管理RAID

1.前Linux上设置RAID0,让我们做一个系统更新,然后安装“mdadm的 '包。 mdadm是一个小程序,它将允许我们在Linux中配置和管理RAID设备。

# yum clean all && yum update
# yum install mdadm -y

安装mdadm工具
在这里插入图片描述

第2步:验证附加的两个20GB驱动器

2.在创建RAID 0,请务必确认检测到所连接的两个硬盘与否,使用下面的命令。

# ls -l /dev | grep sd

检查硬盘驱动器

在这里插入图片描述

  • 3.一旦检测到新的硬盘驱动器,它的时间来检查连接的驱动器是否已经使用任何现有的RAID以下“的mdadm’命令的帮助。
# mdadm --examine /dev/sd[b-c]

检查RAID设备

从输出,我们就知道,没有RAID的已应用到这两个深发展和SDC驱动器。
在这里插入图片描述

第3步:创建RAID的分区

4.现在创建的SDB和SDC分区对于RAID,具有以下fdisk命令的帮助。 在这里,我将展示如何创建sdb的驱动器上的分区。

# fdisk /dev/sdb

按照以下说明创建分区。

按“N”来创建新的分区。
然后选择主分区“P”。
接下来选择分区号为1。
只需按两次回车键给出的默认值。
接着按“P”来打印定义的分区。

创建分区
在这里插入图片描述

按照以下关于在分区上创建Linux raid auto的说明。

按“L”,列出所有可用的类型。
键入“T”来选择分区。
选择“ 的fd”为Linux RAID自动,然后按Enter应用。
然后再次使用“P”打印什么,我们所做的更改。
使用“W”写的变化。

在Linux中创建RAID分区
在这里插入图片描述
注 :请按照相同的上述指示立即创建SDC驱动器上的分区。

5.创建分区后,验证这两个驱动程序都支持RAID使用以下命令正确定义

# mdadm --examine /dev/sd[b-c]
# mdadm --examine /dev/sd[b-c]1

验证RAID分区
在这里插入图片描述

第4步:创建RAID md设备

6.现在创建MD设备(即/ dev / md0的 ),使用下面的命令适用的RAID级别。

# mdadm -C /dev/md0 -l raid0 -n 2 /dev/sd[b-c]1
# mdadm --create /dev/md0 --level=stripe --raid-devices=2 /dev/sd[b-c]1

-C -创建
-l -水平
-n -没有RAID-设备

7.一旦MD设备已经建立,现在验证RAID级别 , 设备和阵列的使用状态,具有如下面的一系列命令的帮助。

# cat /proc/mdstat

验证RAID级别
在这里插入图片描述

# mdadm -E /dev/sd[b-c]1

验证RAID设备
在这里插入图片描述


# mdadm --detail /dev/md0

验证RAID阵列
在这里插入图片描述

第5步:将RAID设备分配给文件系统

8.创建一个RAID设备/ dev / md0的一个EXT4文件系统,并在/ dev / RAID0安装它。

# mkfs.ext4 /dev/md0

创建ext4文件系统
在这里插入图片描述
9.一旦EXT4文件系统已经为RAID设备创建的,现在创建一个挂载点目录(即到/ mnt / RAID0),并在其挂载设备/ dev / md0的 。

# mkdir /mnt/raid0
# mount /dev/md0 /mnt/raid0/

10.接下来,验证设备/ dev / md0的是/ mnt / RAID0目录下使用df命令安装。

# df -h

11.接下来,挂载点的/ mnt / RAID0下创建一个名为“howtoing.txt’文件,添加一些内容到创建的文件,并查看文件和目录的内容。

# touch /mnt/raid0/howtoing.txt
# echo "Hi everyone how you doing ?" > /mnt/raid0/howtoing.txt
# cat /mnt/raid0/howtoing.txt
# ls -l /mnt/raid0/

验证安装设备
在这里插入图片描述

12.一旦你验证挂载点,它的时间来创建在/ etc / fstab文件中的fstab条目。

# vim /etc/fstab

按照描述添加以下条目。 可能根据您使用的安装位置和文件系统而有所不同。

/dev/md0                /mnt/raid0              ext4    defaults         0 0

将设备添加到Fstab

在这里插入图片描述
13.运行安装’-a’来检查,如果在fstab条目的任何错误。

# mount -av

检查Fstab中的错误
在这里插入图片描述

第6步:保存RAID配置

14.最后,突袭配置保存到文件中的一个,以保持配置以供将来使用。 同样,我们使用’mdadm的“与”-s“(扫描)和”-v“(详细)选项命令,如图所示。

# mdadm -E -s -v >> /etc/mdadm.conf
# mdadm --detail --scan --verbose >> /etc/mdadm.conf
# cat /etc/mdadm.conf

保存RAID配置
在这里插入图片描述
就是这样,我们在这里看到,如何使用两个硬盘配置raid级别的RAID0条带化。 在接下来的文章中,我们将看到如何建立RAID5。

四、gettid和pthread_self区别

  • gettid 获取的是内核中真实线程ID, 对于多线程进程来说,每个tid实际是不一样的。

  • 而pthread_self获取的是相对于进程的线程控制块的首地址, 只是用来描述统一进程中的不同线程。pthread_self 即是获取线程控制块tcb首地址,相对于进程数据的段的偏移, 注:pthread_create也是返回该值。

  • gettid 获取的是内核中线程ID,而pthread_self 是posix描述的线程ID。

  • 对于单线程的进程,内核中tid==pid,对于多线程进程,他们有相同的pid,不同的tid。tid用于描述内核真实的pid和tid信息。

  • pthread_self返回的是posix定义的线程ID,man手册明确说明了和内核线程tid不同。它只是用来区分某个进程中不同的线程,当一个线程退出后,新创建的线程可以复用原来的id。

  • 为什么需要两个ID描述线程?通过执行如下代码, 我们也能发现他们的区别:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
//#include <sys/syscall.h> 
 
#define __NR_gettid 186
void *f()
{
	int status;
	printf("begin: pid: %d, tid:%ld, self: %ld\n", getpid(), (long int)syscall(__NR_gettid), pthread_self());
	int ret = fork();
	if(ret == 0){
		printf("[child] pid: %d, tid:%ld, self: %ld\n", getpid(), (long int)syscall(__NR_gettid), pthread_self());
	}else if(ret > 0){
		printf("[parent] pid: %d, tid:%ld, self: %ld\n", getpid(), (long int)syscall(__NR_gettid), pthread_self());
		waitpid(-1, &status, 0);
	}
}
 
int main()
{
	
	int i = 0;
	pthread_t pth[1]; 
	while(i++<1){
		pthread_create(&pth[i], NULL, f, NULL);
		sleep(1);
	}
	pause();
}

在这里插入图片描述
描述线程的id,为什么需要两个不同的ID呢?
这是因为线程库实际上由两部分组成:内核的线程支持+用户态的库支持(glibc),Linux在早期内核不支持线程的时候,glibc就在库中(用户态)以线程(就是用户态线程)的方式支持多线程了,POSIX thread只要求了用户编程的调用接口,对内核接口没有要求。

linux上的线程实现就是在内核支持的基础上以POSIX thread的方式对外封装了接口,所以才会有两个ID的问题。

例子中,在线程中调用fork,只会将当前活动线程设置为活动(其他线程终止),且进程使用的都是虚拟地址,所以产生的pthread_self() 是相同的。

上述不匹配,对程序的实际运行,并没有影响,因为他们的tid是不同的。

五、采用dlopen、dlsym、dlclose加载动态链接库

1.生产动态链接库

编译参数 gcc -fPIC -shared

例如将如下程序编译为动态链接库libcaculate.so,程序如下:

int add(int a,int b)
{
    return (a + b);
}

int sub(int a, int b)
{
    return (a - b);
}

int mul(int a, int b)
{
    return (a * b);
}

int div(int a, int b)
{
    return (a / b);
}

复制代码
编译如下: gcc -fPIC -shared caculate.c -o libcaculate.so
在这里插入图片描述

2.dlopen、dlsym函数介绍

在linux上man dlopen可以看到使用说明,函数声明如下:

#include <dlfcn.h>

void *dlopen(const char *filename, int flag);

char *dlerror(void);

void *dlsym(void *handle, const char *symbol);

int dlclose(void *handle);

dlopen以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程,dlerror返回出现的错误,dlsym通过句柄和连接符名称获取函数名或者变量名,dlclose来卸载打开的库。 dlopen打开模式如下:

RTLD_LAZY 暂缓决定,等有需要时再解出符号
  RTLD_NOW 立即决定,返回前解除所有未决定的符号。

采用上面生成的libcaculate.so,写个测试程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

//动态链接库路径
#define LIB_CACULATE_PATH "./libcaculate.so"

//函数指针
typedef int (*CAC_FUNC)(int, int);

int main()
{
    void *handle;
    char *error;
    CAC_FUNC cac_func = NULL;

    //打开动态链接库
    handle = dlopen(LIB_CACULATE_PATH, RTLD_LAZY);
    if (!handle) {
    fprintf(stderr, "%s\n", dlerror());
    exit(EXIT_FAILURE);
    }

    //清除之前存在的错误
    dlerror();

    //获取一个函数
    *(void **) (&cac_func) = dlsym(handle, "add");
    if ((error = dlerror()) != NULL)  {
    fprintf(stderr, "%s\n", error);
    exit(EXIT_FAILURE);
    }
    printf("add: %d\n", (*cac_func)(2,7));

    cac_func = (CAC_FUNC)dlsym(handle, "sub");
    printf("sub: %d\n", cac_func(9,2));

    cac_func = (CAC_FUNC)dlsym(handle, "mul");
    printf("mul: %d\n", cac_func(3,2));

    cac_func = (CAC_FUNC)dlsym(handle, "div");
    printf("div: %d\n", cac_func(8,2));

    //关闭动态链接库
    dlclose(handle);
    exit(EXIT_SUCCESS);
}

编译选项如下:gcc -rdynamic -o main main.c -ldl

测试结果如下所示:
在这里插入图片描述
注意:

void *dlsym(void *handle, const char *symbol);
返回值为void*
(void **)&(cac_func)是将函数指针的地址强制转换void**类型
然后使用*取值,获取dlsym的返回值
实际这个地方没有必要这样,函数指针本来就是地址,可以直接用
cac_func = dlsym(handle, "add");

六、sysconf函数

在看开源代码的时候,尤其是获取cpu核数的时候,发现了一个很好用的一个函数

 #include <unistd.h>
 
       long sysconf(int name);

通过名字可以猜到,该函数是获取一些系统的参数。

使用下面的一个实例,看一下我的电脑的一些配置信息

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main()
{
	printf("Size of a page in bytes:%ld\n",sysconf(_SC_PAGESIZE));
	printf("Max length of a  hostname:%ld\n",sysconf(_SC_HOST_NAME_MAX));
	printf(" The maximum number of files that a process can have open at any time.:%ld\n",sysconf(_SC_OPEN_MAX));
	printf("  The  number  of  clock  ticks  per  second.:%ld\n",sysconf(_SC_CLK_TCK)); 
	printf("The number of processors currently online .:%ld\n",sysconf(_SC_NPROCESSORS_ONLN)); 
	printf("The number of processors configured..:%ld\n",sysconf(_SC_NPROCESSORS_CONF)); 
	return 0;
}

输出信息:

Size of a page in bytes:4096
Max length of a  hostname:64
 The maximum number of files that a process can have open at any time.:1024
  The  number  of  clock  ticks  per  second.:100
The number of processors currently online .:1
The number of processors configured..:1

七、Linux中ifreq 结构体分析和使用 及其在项目中的简单应用

1.结构原型:

/*
 * Interface request structure used for socket
 * ioctl's.  All interface ioctl's must have parameter
 * definitions which begin with ifr_name.  The
 * remainder may be interface specific.
 */

struct ifreq 
{
#define IFHWADDRLEN 6
 union
 {
  char ifrn_name[IFNAMSIZ];  /* if name, e.g. "en0" */
 } ifr_ifrn;
 
 union {
  struct sockaddr ifru_addr;
  struct sockaddr ifru_dstaddr;
  struct sockaddr ifru_broadaddr;
  struct sockaddr ifru_netmask;
  struct  sockaddr ifru_hwaddr;
  short ifru_flags;
  int ifru_ivalue;
  int ifru_mtu;
  struct  ifmap ifru_map;
  char ifru_slave[IFNAMSIZ]; /* Just fits the size */
  char ifru_newname[IFNAMSIZ];
  void __user * ifru_data;
  struct if_settings ifru_settings;
 } ifr_ifru;
};

#define ifr_name ifr_ifrn.ifrn_name /* interface name  */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address   */
#define ifr_addr ifr_ifru.ifru_addr /* address  */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags  */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric  */
#define ifr_mtu  ifr_ifru.ifru_mtu /* mtu   */
#define ifr_map  ifr_ifru.ifru_map /* device map  */
#define ifr_slave ifr_ifru.ifru_slave /* slave device  */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue    /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length  */
#define ifr_newname ifr_ifru.ifru_newname /* New name  */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/

2.基本介绍

ifreq结构定义在/usr/include/net/if.h,用来配置ip地址,激活接口,配置MTU等接口信息的。其中包含了一个接口的名字和具体内容——(是个共用体,有可能是IP地址,广播地址,子网掩码,MAC号,MTU或其他内容)。ifreq包含在ifconf结构中。而ifconf结构通常是用来保存所有接口的信息的。

3.举例说明:

在Linux系统中,ifconfig命令是通过ioctl接口与内核通信,
例如,当系统管理员输入如下命令来改变接口eth0的MTU大小:

ifconfig eth0 mtu 1250

ifconfig命令首先打开一个socket,然后通过系统管理员输入的参数初始化一个数据结构,并通过ioctl调用将数据传送到内核。SIOCSIFMTU是命令标识符。

struct ifreq data;
fd = socket(PF_INET, SOCK_DGRAM, 0);
< ... initialize "data" ...>
err = ioctl(fd, SIOCSIFMTU, &data);

4.其它eg,参考:

https://www.cnblogs.com/landy-weiai/p/3752665.html

参考

https://blog.csdn.net/Geek_sun/article/details/83281105
https://www.jb51.net/article/117873.htm
https://www.cnblogs.com/Neddy/archive/2012/02/02/2335343.html
https://www.jianshu.com/p/86ed6a7cf34b

https://www.cnblogs.com/liuyansheng/p/6137464.html
https://www.howtoing.com/create-raid0-in-linux/
https://blog.csdn.net/rsyp2008/article/details/45150621
https://www.cnblogs.com/Anker/p/3746802.html
https://blog.csdn.net/wallwind/article/details/49696021
https://www.cnblogs.com/landy-weiai/p/3752665.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜欢打篮球的普通人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值