ThreadX应用笔记:内核初始化和任务调度

作者:zzssdd2

E-mail:zzssdd2@foxmail.com

一、前言

了解 ThreadX 的初始化流程有助于移植使用,掌握任务的的调度有助于更加得心应手地运用该实时操作系统。

二、初始化流程

官方手册给出的 ThreadX 初始化流程如下图所示:

STM32F429 + MDK-ARM-AC6 平台举例,初始化流程的前3步在 startup_stm32f429xx.s 文件中完成 ,该启动文件内容是STM32复位后首先执行的,主要完成以下操作:

  • 设置堆栈( __initial_sp、__heap_base、__heap_limit)
  • 设置中断向量表(__Vectors)
  • 设置复位入口程序(Reset_Handler)
  • 链接到C库中的__main,最终进入到 main() 函数

进入 main() 函数后开始进行ThreadX的x相关初始化,后面几步更详细的调用流程如下所示:

关于STM32移植ThreadX更详细的过程可以参考这篇文章: ThreadX移植——STM32H7+MDK-AC6平台

三、线程状态

理解线程的不同处理状态是理解整个多线程环境的关键因素。在 ThreadX 中包含5种不同的线程状态: 就绪挂起执行终止完成 。下图展示了ThreadX的线程状态转换图:

  • 当线程准备执行时处于就绪状态,就绪线程只有在它是处于就绪线程列表的最高优先级时才会执行。当线程开始执行时其状态由就绪态转为运行态
  • 如果更高优先级的线程准备就绪,则执行线程将恢复到就绪状态。然后执行新准备好的高优先级线程,将其逻辑状态更改为运行态。每次发生线程抢占时,都会发生就绪状态和运行状态之间的转换。
  • 在任何给定的时刻,只有一个线程处于执行状态。这是因为处于执行状态的线程对底层处理器具有控制权。
  • 处于挂起状态的线程不符合执行条件。处于挂起状态的原因包括时间挂起、队列消息、信号量、互斥、事件标志、内存和基本线程挂起。在清除挂起的原因后,线程将恢复到就绪状态。
  • 处于完成状态的线程是已完成其任务处理并从其入口函数返回的线程。 入口函数是在线程创建期间指定的。 处于完成状态的线程无法再次执行。
  • 线程处于终止状态是因为另一个线程或是线程本身调用 tx_thread_terminate 服务。线程处于终止状态将无法再次执行。
  • 如果需要重新启动已完成或终止的线程,应用程序必须首先删除该线程。然后可以重新创建并重新启动它。

四、配置选项

在使用 ThreadX 时,有一些配置选项可供选择。这些选项包含在 tx_user.h 文件中。要使用这些配置项的前提是工程编译时包含 TX_INCLUDE_USER_DEFINE_FILE 宏定义。

  • TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO

    定义后,此选项可在字节池上收集性能信息。默认情况下,未定义此选项。

  • TX_DISABLE_ERROR_CHECKING

    绕过基本服务呼叫错误检查。在应用程序源中定义时,将禁用所有基本参数错误检查。这可以将性能提高多达30%,并且还可以减小镜像大小。

    注意:只有在应用程序可以绝对保证所有输入参数在所有情况下始终有效的情况下,才可以禁用错误检查,包括从外部输入派生的输入参数。如果在禁用错误检查的情况下向API提供了无效的输入,则导致的行为是不确定的,并且可能导致内存损坏或系统崩溃。

    注意:不受禁用错误检查影响的ThreadX API返回值在第4章的每个API描述的“返回值”部分中以粗体列出。如果通过使用TX_DISABLE_ERROR_CHECKING选项禁用了错误检查,则非粗体返回值将无效。

  • TX_DISABLE_NOTIFY_CALLBACKS

    定义后,将为各种ThreadX对象禁用通知回调。使用此选项可略微减小代码大小并提高性能。默认情况下,未定义此选项。

  • TX_DISABLE_PREEMPTION_THRESHOLD

    定义后,将禁用抢占阈值功能,并略微减小代码大小并提高性能。当然,抢占阈值功能不再可用。默认情况下,未定义此选项。

  • TX_DISABLE_REDUNDANT_CLEARING

    定义后,删除用于将ThreadX全局C数据结构初始化为零的逻辑。仅当编译器的初始化代码将所有未初始化的C全局数据设置为零时,才应使用此方法。使用此选项可略微减小代码大小并提高初始化期间的性能。默认情况下,未定义此选项。

  • TX_DISABLE_STACK_FILLING

    定义时,禁用在创建时将0xEF值放置在每个线程堆栈的每个字节中。默认情况下,未定义此选项。

  • TX_ENABLE_EVENT_TRACE

    定义后,ThreadX启用事件收集代码以创建TraceX跟踪缓冲区。

  • TX_ENABLE_STACK_CHECKING

    定义后,将启用ThreadX运行时堆栈检查,其中包括分析已使用了多少堆栈以及检查堆栈区域之前和之后的数据模式“围栏”。如果检测到堆栈错误,则会调用已注册的应用程序堆栈错误处理程序。此选项的确会导致开销和代码大小略有增加。查看 tx_thread_stack_error_notify API函数以获取更多信息。默认情况下,未定义此选项。

  • TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO

    定义后,可以收集有关事件标志组的性能信息。默认情况下,未定义此选项。

  • TX_INLINE_THREAD_RESUME_SUSPEND

    定义后,ThreadX通过内联代码改进 tx_thread_resume tx_thread_suspend API调用。这增加了代码大小,但增强了这两个API调用的性能。

  • TX_MAX_PRIORITIES

    定义ThreadX的优先级。合法值的范围是32到1024(含),并且 必须 能被32整除。增加的优先级级别数对于每组32个优先级将RAM使用量增加128字节。但是,对性能的影响可忽略不计。默认情况下,此值设置为32个优先级。

  • TX_MINIMUM_STACK

    定义最小堆栈大小(以字节为单位)。创建线程时,用于错误检查。默认值是特定于端口的,可以在 tx_port.h中 找到。

  • TX_MISRA_ENABLE

    定义后,ThreadX将使用符合MISRA C的约定。有关详细信息,请参考 ThreadX_MISRA_Compliance.pdf

  • TX_MUTEX_ENABLE_PERFORMANCE_INFO

    定义后,可以收集互斥量上的性能信息。默认情况下,未定义此选项。

  • TX_NO_TIMER

    定义后,将完全禁用ThreadX计时器逻辑。在不使用ThreadX计时器功能(线程睡眠,API超时,时间分片和应用程序计时器)的情况下,这很有用。如果 TX_NO_TIMER 指定,则选项 TX_TIMER_PROCESS_IN_ISR 也必须定义。

  • TX_NOT_INTERRUPTABLE

    定义后,ThreadX不会尝试最小化中断锁定时间。这样可以加快执行速度,但会略微增加中断锁定时间。

  • TX_QUEUE_ENABLE_PERFORMANCE_INFO

    定义后,可以在队列上收集性能信息。默认情况下,未定义此选项。

  • TX_REACTIVATE_INLINE

    定义后,直接内联执行ThreadX计时器,而不使用函数调用。这样可以提高性能,但会稍微增加代码大小。默认情况下,未定义此选项。

  • TX_SEMAPHORE_ENABLE_PERFORMANCE_INFO

    定义后,可以收集有关信号量的性能信息。默认情况下,未定义此选项。

  • TX_THREAD_ENABLE_PERFORMANCE_INFO

    定义后,可以在线程上收集性能信息。默认情况下,未定义此选项。

  • TX_TIMER_ENABLE_PERFORMANCE_INFO

    定义后,可在计时器上收集性能信息。默认情况下,未定义此选项。

  • TX_TIMER_PROCESS_IN_ISR

    定义后,消除ThreadX的内部系统计时器线程。由于不再需要定时器堆栈和控制块,因此可以提高定时器事件的性能,并减少对RAM的需求。但是,使用此选项会将所有计时器到期处理移至计时器ISR级别。默认情况下,未定义此选项。

    注意:ISR可能不允许计时器允许的服务,因此使用此选项时可能不允许。

  • TX_TIMER_THREAD_PRIORITY

    定义内部ThreadX系统计时器线程的优先级。默认值是优先级0,即ThreadX中的最高优先级。默认值在 tx_port.h中 定义。

  • TX_TIMER_THREAD_STACK_SIZE

    定义内部ThreadX系统计时器线程的堆栈大小(以字节为单位)。该线程处理所有线程睡眠请求以及所有服务调用超时。此外,所有应用程序计时器回调例程都从该上下文中调用。默认值是特定于端口的,可以在 tx_port.h中 找到。

五、任务相关API

1、创建应用程序线程

  • 描述

    该服务创建一个应用程序线程,该线程在指定的任务输入功能处开始执行。堆栈,优先级,抢占阈值和时间片是输入参数指定的属性。另外,还指定了线程的初始执行状态。

  • 参数

    • thread_ptr指向线程控制块的指针。

    • name_ptr指向线程名称的指针。

    • entry_function指定用于线程执行的初始C函数。当线程从此入口函数返回时,它将处于 完成 状态并无限期挂起。

    • entry_input一个32位值,该值在首次执行时传递给线程的入口函数。此输入的使用完全由应用程序确定。

    • stack_start堆栈存储区的起始地址。

    • stack_size堆栈存储区中的字节数。线程的堆栈区域必须足够大以处理最坏情况的函数调用嵌套和局部变量使用。

    • priority线程的数字优先级。有效值的范围是0到(TX_MAX_PRIORITES-1),其中0表示最高优先级。

    • preempt_threshold禁用的抢占的最高优先级(0到(TX_MAX_PRIORITIES-1))。只有高于此级别的优先级才可以抢占该线程。该值必须小于或等于指定的优先级。等于线程优先级的值将禁用抢占阈值。

    • time_slice允许在同一优先级的其他就绪线程运行之前,允许该线程运行的计时器计数。请注意,使用抢占阈值将禁用时间片。合法的时间片值范围是1到0xFFFFFFFF(包括0)。值为 TX_NO_TIME_SLICE (值为0)禁用此线程的时间切片。

      注意:使用时间分片会导致少量系统开销。由于时间片仅在多个线程共享相同优先级的情况下才有用,因此不应将具有唯一优先级的线程分配时间片。

    • auto_start指定线程是立即启动还是处于挂起状态。有效选项为 TX_AUTO_START (0x01)和 TX_DONT_START (0x00)。如果指定了TX_DONT_START,则应用程序以后必须调用tx_thread_resume才能运行线程。

  • 返回值

    • TX_SUCCESS (0x00)成功创建线程。
    • TX_THREAD_ERROR (0x0E)无效的线程控制指针。指针为NULL或线程已创建。
    • TX_PTR_ERROR (0x03)入口点或堆栈区域的无效起始地址无效,通常为NULL。
    • TX_SIZE_ERROR (0x05)堆栈区域的大小无效。线程必须至少具有 TX_MINIMUM_STACK 字节才能执行。
    • TX_PRIORITY_ERROR (0x0F)无效的线程优先级,该值超出(0到(TX_MAX_PRIORITIES-1))的范围。
    • TX_THRESH_ERROR (0x18)指定了无效的抢占 阈值 。该值的有效优先级必须小于或等于线程的初始优先级。
    • TX_START_ERROR (0x10)无效的自动启动选择。
    • TX_CALLER_ERROR (0x13)该服务的无效调用者。
UINT tx_thread_create(
    TX_THREAD *thread_ptr,
    CHAR *name_ptr, 
    VOID (*entry_function)(ULONG),
    ULONG entry_input, 
    VOID *stack_start,
    ULONG stack_size, 
    UINT priority,
    UINT preempt_threshold, 
    ULONG time_slice,
    UINT auto_start);

2、删除应用程序线程

  • 描述

    该服务删除指定的应用程序线程。由于指定的线程必须处于终止或完成状态,因此无法从试图删除自身的线程中调用此服务。

    注意:应用程序负责管理与线程的堆栈关联的内存区域,此服务完成后才可用。此外,应用程序必须阻止使用已删除的线程。

  • 参数

    thread_ptr指向先前创建的应用程序线程的指针。

  • 返回值

    • TX_SUCCESS (0x00)成功删除线程。
    • TX_THREAD_ERROR (0x0E)无效的应用程序线程指针。
    • TX_DELETE_ERROR (0x11)指定的线程未处于终止或完成状态。
    • TX_CALLER_ERROR (0x13)该服务的无效调用者。
UINT tx_thread_delete(TX_THREAD *thread_ptr);

3、在线程进入和退出时通知应用程序

  • 描述

    此服务注册一个通知回调函数,该函数将在进入或退出指定线程时调用。通知回调的处理由应用程序定义。

    注意:不允许应用程序的线程进入/退出通知回调调用具有暂停选项的任何ThreadX API。

  • 参数

    • thread_ptr 指向先前创建的线程的指针。
    • entry_exit_notify 指向应用程序的线程进入/退出通知功能的指针。进入/退出通知功能的第二个参数指定是否存在进入或退出。值 TX_THREAD_ENTRY (0x00)表示已进入线程,而值 TX_THREAD_EXIT (0x01)表示已退出线程。如果此值为 TX_NULL ,则禁用通知。
  • 返回值

    • TX_SUCCESS (0x00)成功注册线程进入/退出通知功能。
    • TX_THREAD_ERROR (0x0E)无效的线程指针。
    • TX_FEATURE_NOT_ENABLED (0xFF)系统在禁用通知功能的情况下进行了编译。
UINT tx_thread_entry_exit_notify(
    TX_THREAD *thread_ptr,
    VOID (*entry_exit_notify)(TX_THREAD *, UINT));

4、查询当前正在执行的任务

  • 描述

    该服务返回指向当前正在执行的线程的指针。如果没有线程在执行,则此服务返回空指针。

    注意:如果从ISR调用此服务,则返回值表示在执行中断处理程序之前运行的线程。

  • 参数

  • 返回值

    thread pointer指向当前正在执行的线程的指针。如果没有线程在执行,则返回值为 TX_NULL

TX_THREAD* tx_thread_identify(VOID);

5、查询任务相关信息

  • 描述

    该服务检索有关指定线程的信息。

  • 参数

    • thread_ptr指向线程控制块的指针。

    • name指向目标的指针,该目标指向线程名称。

    • state指向线程当前执行状态的目标的指针。可能的值如下。

      • TX_READY (0x00)
      • TX_COMPLETED (0x01)
      • TX_TERMINATED (0x02)
      • TX_SUSPENDED (0x03)
      • TX_SLEEP (0x04)
      • TX_QUEUE_SUSP (0x05)
      • TX_SEMAPHORE_SUSP (0x06)
      • TX_EVENT_FLAG (0x07)
      • TX_BLOCK_MEMORY (0x08)
      • TX_BYTE_MEMORY (0x09)
      • TX_MUTEX_SUSP (0x0D)
    • run_count指向目标的线程运行计数。

    • **priority **指向目标的线程优先级。

    • preemption_threshold指向线程的抢占阈值的目标的指针。

    • time_slice指向线程时间切片目标的指针。

    • next_thread指向下一个创建的线程指针的目标的指针。

    • suspend_thread指向目标的指针,该指针指向指向 暂挂 列表中下一个线程的指针。

    注意:为任何参数提供TX_NULL表示该参数不是必需的。

  • 返回值

    • TX_SUCCESS (0x00)成功的线程信息检索。
    • TX_THREAD_ERROR (0x0E)无效的线程控制指针。
UINT tx_thread_info_get(
    TX_THREAD *thread_ptr, 
    CHAR **name,
    UINT *state, 
    ULONG *run_count,
    UINT *priority,
    UINT *preemption_threshold,
    ULONG *time_slice,
    TX_THREAD **next_thread,
    TX_THREAD **suspended_thread);

6、更改线程的抢占阈值

  • 描述

    此服务更改指定线程的抢占阈值。抢占阈值可防止等于或小于抢占阈值的线程抢占指定线程。

    注意:使用抢占阈值会禁用指定线程的时间片。

  • 参数

    • thread_ptr 指向先前创建的应用程序线程的指针。
    • new_threshold 新的抢占阈值优先级(0到(TX_MAX_PRIORITIES-1)。
    • old_threshold 指向要返回先前抢占阈值的位置的指针。
  • 返回值

    • TX_SUCCESS (0x00)成功的抢占阈值更改。
    • TX_THREAD_ERROR (0x0E)无效的应用程序线程指针。
    • TX_THRESH_ERROR (0x18)指定的新抢占阈值不是有效的线程优先级((从(0到( TX_MAX_PRIORITIES -1)以外的值))或大于当前线程优先级((较低的优先级))。
    • TX_PTR_ERROR (0x03)指向先前抢占 阈值 存储位置的无效指针。
    • TX_CALLER_ERROR (0x13)该服务的无效调用者。
UINT tx_thread_preemption_change(
    TX_THREAD *thread_ptr,
    UINT new_threshold, 
    UINT *old_threshold);

7、更改线程的优先级

  • 描述

    该服务更改指定线程的优先级。有效优先级的范围是0到(TX_MAX_PRIORITES-1),其中0表示最高优先级。

    注意:指定线程的抢占阈值将自动设置为新优先级。如果需要新的阈值,则必须在此调用之后使用 tx_thread_preemption_change *服务

  • 参数

    • thread_ptr 指向先前创建的应用程序线程的指针。
    • new_priority 新线程优先级(0到(TX_MAX_PRIORITIES-1))。
    • old_priority 指向一个位置的指针,以返回线程先前的优先级
  • 返回值

    • TX_SUCCESS (0x00)成功的优先级更改。
    • TX_THREAD_ERROR (0x0E)无效的应用程序线程指针。
    • TX_PRIORITY_ERROR (0x0F)指定的新优先级无效(((0到(TX_MAX_PRIORITIES-1)以外的值))。
    • TX_PTR_ERROR (0x03)指向先前优先级存储位置的无效指针。
    • TX_CALLER_ERROR (0x13)该服务的无效调用者。
UINT tx_thread_priority_change(
    TX_THREAD *thread_ptr,
    UINT new_priority, 
    UINT *old_priority);

8、线程释放控制权

  • 描述

    • 此服务放弃处理器的控制权,转交给其他相同或更高优先级的处于就绪状态的线程

    • 除了将控制权放弃给相同优先级的线程之外,此服务还将控制权放弃给由于当前线程的抢占阈值设置而阻止执行的最高优先级线程。

  • 参数

  • 返回值

VOID tx_thread_relinquish(VOID);

9、重置线程

  • 描述

    此服务将指定的线程重置为在线程创建时定义的入口点执行。线程必须处于 TX_COMPLETEDTX_TERMINATED 状态才能重置

    注意:必须恢复(resume)该线程以使其再次执行

  • 参数

    • thread_ptr 指向先前创建的线程的指针。
  • 返回值

    • TX_SUCCESS (0x00)成功的线程重置。
    • TX_NOT_DONE (0x20)指定的线程不处于 TX_COMPLETEDTX_TERMINATED 状态。
    • TX_THREAD_ERROR (0x0E)无效的线程指针。
    • TX_CALLER_ERROR (0x13)该服务的无效调用者。
UINT tx_thread_reset(TX_THREAD *thread_ptr);

10、恢复线程

  • 描述

    该服务将恢复或准备执行先前由 tx_thread_suspend 调用暂停的线程。此外,此服务将恢复在没有自动启动的情况下创建的线程。

  • 参数

    • thread_ptr 指向挂起的应用程序线程的指针。
  • 返回值

    • TX_SUCCESS (0x00)成功的线程恢复。
    • TX_SUSPEND_LIFTED (0x19)先前设置的延迟暂停已取消。
    • TX_THREAD_ERROR (0x0E)无效的应用程序线程指针。
    • TX_RESUME_ERROR (0x12)指定的线程没有被挂起,或者之前被 tx_thread_suspend 以外的服务挂起。
UINT tx_thread_resume(TX_THREAD *thread_ptr);

11、挂起线程

  • 描述

    此服务挂起指定的应用程序线程。线程可以调用此服务来挂起自身。

    挂起后,必须由 tx_thread_resume 恢复该线程才能再次执行。

    注意:如果指定的线程由于其他原因已经挂起,则此挂起操作将在内部保持,直到先前的挂起解除为止。当发生这种情况时,将执行指定线程的无条件挂起。进一步的无条件中止请求无效。

  • 参数

    • thread_ptr 指向应用程序线程的指针。
  • 返回值

    • TX_SUCCESS (0x00)成功的线程挂起。
    • TX_THREAD_ERROR (0x0E)无效的应用程序线程指针。
    • TX_SUSPEND_ERROR (0x14)指定的线程处于终止或完成状态。
    • TX_CALLER_ERROR (0x13)该服务的无效调用者。
UINT tx_thread_suspend(TX_THREAD *thread_ptr);

12、将当前线程挂起指定的时间

  • 描述

    此服务使调用线程在指定的计时器刻度数内挂起。与计时器刻度相关的物理时间量是特定于应用程序的。只能从应用程序线程调用此服务。

  • 参数

    • timer_ticks 用于暂停调用应用程序线程的计时器滴答数,范围从0到0xFFFFFFFF。如果指定0,则服务将立即返回。
  • 返回值

    • TX_SUCCESS (0x00)成功的线程睡眠。
    • TX_WAIT_ABORTED (0x1A)暂停被另一个线程,计时器或ISR中止。
    • TX_CALLER_ERROR (0x13)从非线程调用的服务。
UINT tx_thread_sleep(ULONG timer_ticks);

13、线程堆栈错误通知回调

  • 描述

    该服务注册了一个通知回调函数来处理线程堆栈错误。当ThreadX在执行过程中检测到线程堆栈错误时,它将调用此通知函数来处理错误。错误的处理完全由应用程序定义。从暂停违规线程到重置整个系统,任何事情都可以完成。

    注意:必须定义 TX_ENABLE_STACK_CHECKING 配置选项,以便此服务返回性能信息。

  • 参数

    • error_handler 指向应用程序的堆栈错误处理功能的指针。如果此值为TX_NULL,则禁用通知。
  • 返回值

    • TX_SUCCESS (0x00)成功的线程重置。
    • TX_FEATURE_NOT_ENABLED (0xFF)系统未在启用性能信息的情况下进行编译。
UINT tx_thread_stack_error_notify(VOID (*error_handler)(TX_THREAD *));

14、终止线程

  • 描述
    • 该服务终止指定的应用程序线程,而不管该线程是否被挂起。线程可以调用此服务以终止自身。
    • 应用程序有责任确保线程处于适合终止的状态。例如,线程不应在关键应用程序处理期间或在其他中间件组件内部终止,否则可能会使这种处理处于未知状态。 **
    • 终止后,必须重置线程以使其再次执行。
  • 参数
    • thread_ptr 指向应用程序线程的指针。
  • 返回值
    • TX_SUCCESS (0x00)成功终止线程。
    • TX_THREAD_ERROR (0x0E)无效的应用程序线程指针。
    • TX_CALLER_ERROR (0x13)该服务的无效调用者。
UINT tx_thread_terminate(TX_THREAD *thread_ptr);

15、更改线程的时间片

  • 描述

    该服务更改指定应用程序线程的时间片。为线程选择时间片可确保在相同或更高优先级的其他线程有机会执行之前,它不会执行超过指定数量的计时器滴答。

    注意:使用抢占阈值会禁用指定线程的时间片。

  • 参数

    • thread_ptr 指向应用程序线程的指针。
    • new_time_slice 新的时间片值。合法值包括TX_NO_TIME_SLICE和从1到0xFFFFFFFF的数值。
    • old_time_slice 指向用于存储指定线程的先前时间片值的位置的指针。
  • 返回值

    • TX_SUCCESS (0x00)成功的时间切片机会。
    • TX_THREAD_ERROR (0x0E)无效的应用程序线程指针。
    • TX_PTR_ERROR (0x03)指向先前时间片存储位置的无效指针。
    • TX_CALLER_ERROR (0x13)该服务的无效调用者。
UINT tx_thread_time_slice_change(
    TX_THREAD *thread_ptr,
    ULONG new_time_slice, 
    ULONG *old_time_slice);

16、中止指定线程

  • 描述

    此服务中止睡眠或指定线程的任何其他对象挂起。如果等待被中止,则线程正在等待的服务将返回 TX_WAIT_ABORTED 值。

    注意:该服务不会释放由tx_thread_suspend服务进行的显式挂起。

  • 参数

    • thread_ptr 指向先前创建的应用程序线程的指针。
  • 返回值

    • TX_SUCCESS (0x00)成功的线程等待中止。
    • TX_THREAD_ERROR (0x0E)无效的应用程序线程指针。
    • TX_WAIT_ABORT_ERROR (0x1B)指定的线程未处于等待状态。
UINT tx_thread_wait_abort(TX_THREAD *thread_ptr);
#感谢您访问本站#
#本文转载自互联网,若侵权,请联系删除,谢谢!657271#qq.com#