CS61C|Lec16-OS
本文最后更新于 142 天前,其中的信息可能已经有所发展或是发生改变。

操作系统的基本功能

操作系统是计算机启动时运行的第一个程序。

查找并控制所有设备

  • 操作系统会发现并控制计算机中的所有硬件设备,例如硬盘、网络接口、显示器、键盘、鼠标等。因为每个设备的操作方式不同,操作系统依赖设备驱动程序(device drivers)来与不同的硬件进行通信。设备驱动程序是硬件制造商提供的特定程序,用于帮助操作系统与硬件正确交互。

启动服务(100多个):

- 文件系统,

- 网络协议栈(以太网、WiFi、蓝牙等),

- TTY(键盘),等等

加载、运行和管理程序:

  • 多任务处理:操作系统能够同时运行多个程序,这是通过**时间分片(time-sharing)实现的。时间分片让每个程序轮流使用CPU资源,看起来像是它们在同时运行。

  • 进程隔离:每个程序在操作系统中都有自己的“进程”,操作系统确保每个进程的内存和资源不会互相干扰。这种隔离使得即便一个程序崩溃,也不会影响其他程序的运行。

  • 资源多路复用:操作系统管理不同的程序之间如何共享硬件资源(例如CPU、内存、硬盘和网络),确保每个程序都能公平地使用系统资源。

操作系统核心功能:

  • 进程隔离:操作系统的一个核心功能是为每个运行中的程序提供隔离。每个程序在它自己的“世界”中运行,无法直接访问其他程序的内存或资源。这种隔离防止了一个程序的错误影响其他程序的运行。例如,一个程序崩溃时,操作系统确保其他程序依然可以正常工作。

  • 与外界的交互:操作系统还负责处理程序与外部设备的交互,包括磁盘、显示器、网络等。程序不能直接操作这些设备,而是通过操作系统的设备管理模块与设备交互。例如,当程序需要读取文件时,它会请求操作系统进行文件系统操作。

操作系统对硬件的需求:

1. 内存转换(Memory Translation)

  • 现代操作系统使用虚拟内存,每个进程看到的都是一个虚拟地址空间,但实际使用的内存是物理地址。操作系统依赖硬件的**内存管理单元(MMU)**来将虚拟地址转换为物理地址。

  • 每个进程有独立的虚拟地址到物理地址的映射,这样进程之间无法直接访问彼此的内存。程序在进行数据加载或存储时,它发出的虚拟地址会通过硬件转化为实际的物理地址。

2. 保护与权限(Protection and Privilege)

  • 处理器一般分为至少两个运行模式:用户模式监督模式(RISC-V架构还有一个更高级的“机器模式”)。在用户模式下,程序的权限有限,无法进行关键的系统操作,例如更改内存映射或直接访问硬件设备。

  • 只有操作系统内核在监督模式下运行时,才能执行这些操作。例如,监督模式可以修改虚拟地址到物理地址的映射,确保进程获得正确的内存分配。

  • 用户模式的程序只能通过操作系统提供的接口(如系统调用)访问系统资源,确保安全和稳定性。

3. 陷阱与中断(Traps & Interrupts)

  • 中断是处理外部事件的机制,例如键盘输入、网络数据传输或硬盘读写完成时,硬件会向操作系统发出中断信号,操作系统中断当前进程的运行去处理这些事件。

  • 陷阱是程序在执行过程中触发的一种异常情况,例如非法内存访问或除零错误,处理器通过陷阱机制进入操作系统的监督模式来处理这些异常情况。

  • 通过中断和陷阱机制,操作系统可以在需要时自动进入监督模式,处理外部事件或异常,再恢复到用户模式继续运行程序。

后面我们会详细的介绍。

电脑启动时会发生什么?

1. 计算机启动时的第一步:CPU开始执行指令

  • 当你打开计算机时,CPU开始从一个预定义的地址执行指令。在这段说明中提到的“0x2000”就是程序计数器(PC)开始执行的默认地址。这些指令通常存储在计算机的Flash ROM中(也称为固件或BIOS)。它的任务是初始化系统并开始加载操作系统。

2. 启动过程的步骤:

1. BIOS:

  • BIOS(基本输入输出系统)是计算机上电后运行的第一个固件程序。它负责找到一个存储设备(通常是硬盘或固态硬盘),并从该设备的第一个扇区读取启动信息。

  • BIOS的任务是确保所有基本硬件设备正常工作,并为后续的操作系统加载做好准备。它还检查系统的硬件配置,如内存、处理器、硬盘等。

2. 引导程序(Bootloader):

  • BIOS会将引导程序(通常存储在硬盘或SSD中)加载到内存中,并将控制权移交给它。

  • 引导程序的任务是加载操作系统的内核。它会将操作系统的核心部分从存储设备中加载到内存中,并跳转到操作系统的入口点,开始执行操作系统代码。

3. 操作系统启动:

  • 一旦操作系统内核被加载到内存中,操作系统开始启动并初始化服务驱动程序。这包括:

  • 初始化文件系统(用于存储和读取文件)。

  • 初始化网络堆栈(以太网、WiFi、蓝牙等)。

  • 加载设备驱动程序,使操作系统能够与硬件交互。

4. 初始化(Init):

  • 操作系统启动后,它会启动一个名为Init的进程,这是系统中的第一个用户空间进程。Init进程负责启动其他系统服务,并启动一个等待用户输入的界面,例如终端(Terminal)或桌面环境。

  • 这一步标志着操作系统已经完成了启动过程,用户可以开始与系统进行交互。

3. 启动过程的关键细节

  • 程序计数器(PC):程序计数器负责跟踪CPU当前执行的指令地址。在启动时,PC通常被设置为一个默认的地址(如0x2000),从这个地址开始执行存储在ROM中的指令。

  • 内存映射(Memory Mapped I/O):CPU在启动时通过内存映射来访问外部设备和内存,这是一种将设备地址映射到内存地址空间的方法。这样,设备可以通过标准的内存访问指令进行操作。

应用程序的启动

1. 进程和线程

  • 进程(Process):在大多数操作系统中,应用程序被称为进程。每个进程都有自己独立的内存空间,这意味着进程之间是隔离的,一个进程不能直接访问或修改另一个进程的内存。这种隔离确保了安全性和稳定性,即使一个进程崩溃,也不会影响其他进程。

  • 线程(Thread):线程是进程中的轻量级任务,多个线程可以共享同一个进程的内存空间(例如全局变量和堆)。线程可以被理解为一个执行路径,一个进程可以包含多个线程,它们可以并发执行并共享资源。

  • 并发运行:虽然进程和线程都是独立执行的,但在单核处理器上它们是通过快速的时间分片来交替执行的,看起来像是同时运行,这种现象叫做伪并发。在多核处理器上,不同的进程或线程则可以真正地并行运行。

2. 应用程序的启动

  • 父进程创建子进程:在大多数操作系统中,一个进程是由另一个进程创建的。通常,shell(命令行终端)就是这样一个负责启动其他应用程序的父进程。

  • 系统调用(syscall):为了启动一个新的进程,父进程会调用操作系统提供的系统调用。不同的操作系统有不同的机制。例如,在Linux中,常用的系统调用是:

  1. fork:创建一个新的进程,称为子进程。子进程最初是父进程的一个副本。

  2. execve:这个系统调用用于在子进程中加载并执行新的程序。当fork创建子进程后,execve将新的程序(如你想运行的应用程序)加载到该子进程中,开始执行。

3. 加载可执行文件

  • 加载应用程序到内存:当程序被加载时,操作系统会从磁盘中读取该应用程序的可执行文件。这些可执行文件包含程序的指令和数据,它们通常被分为不同的部分:

  • .text段:包含程序的指令代码。

  • .data段:包含程序的全局变量和已初始化的数据。

  • 操作系统会将这些指令和数据放入内存中,并为程序准备好堆栈(stack)和堆(heap)。堆栈用于处理函数调用和局部变量,而堆用于动态分配内存。

4. 设置参数并启动程序

  • 操作系统还会为程序设置入口参数(如argcargv),这些参数是从命令行传递给程序的:

  • argc:表示命令行参数的个数。

  • argv:是一个数组,存储了每个命令行参数的内容。

  • 然后,程序从其main函数开始执行。操作系统会将控制权交给该程序,并等待其运行完毕。

5. 等待程序结束

  • 当应用程序执行完毕时,它会返回控制权给其父进程(例如shell)。父进程会通过调用join函数来等待子进程结束,并回收它的资源。

一些后面需要了解的术语

1. 中断(Interrupt)

  • 定义:中断是由正在运行的程序之外的外部事件触发的。它告诉CPU需要暂停当前程序,去处理外部世界的事件。

  • 举例

  • 键盘输入

  • 磁盘I/O操作完成

  • 异步(Asynchronous):中断是异步的,意味着它可以在程序执行的任何时间点发生。它不需要与当前指令的执行同步。

  • 处理方式

  • 可以在任何方便的时刻处理中断,但不能拖延太久,必须及时响应。

2. 异常(Exception)

  • 定义:异常是由程序在执行过程中发生的错误或特殊情况引发的。通常是由于程序尝试执行非法操作或发生了硬件问题。

  • 举例

  • 内存错误(如访问非法地址)

  • 总线错误(硬件连接或传输问题)

  • 非法指令(尝试执行无效的或权限不足的指令)

  • 同步(Synchronous):异常是同步的,意味着它总是在执行特定指令时发生。异常需要立即被处理,不能延迟。

  • 处理方式

  • 必须精确地在引发异常的那条指令上进行处理,不能跳过或延迟。操作系统会立即采取行动,处理异常并决定如何继续。

3. 陷阱(Trap)

  • 定义:陷阱是硬件为处理中断或异常而执行的动作。它会导致程序跳转到专门的“中断或陷阱处理程序”代码,来处理这些情况。

  • 作用

c 当发生中断或异常时,硬件自动将控制权移交给操作系统或硬件的陷阱处理程序,以便进行适当的处理。

监督模式(Supervisor Mode)

1. 监督模式是什么?

  • 监督模式是现代CPU提供的一种特殊运行模式,用来保护操作系统和硬件资源不被应用程序随意访问和修改。在监督模式下,CPU拥有完全的权限,可以访问所有的硬件资源和执行特殊的指令。

  • 这个模式通常是操作系统内核在管理系统时使用的,用户级应用程序无法直接进入监督模式。

2. 为什么需要监督模式?

  • 防止应用程序崩溃影响整个系统:如果一个应用程序运行时出现问题(例如非法操作或内存溢出),可能会导致整个系统崩溃。监督模式的存在可以防止应用程序直接访问关键资源(例如内存或硬盘),从而避免错误或恶意操作影响到其他应用程序或操作系统。

  • 抵御恶意软件(malware):恶意软件可能试图通过直接访问系统资源来破坏系统或窃取数据。通过在非监督模式下运行应用程序,操作系统能够控制其访问权限,保护系统免受恶意软件的威胁。

3. 用户模式与监督模式的区别

  • 用户模式(User Mode):在用户模式下,程序只能访问有限的硬件资源和系统指令。用户模式的程序无法直接操作关键的系统资源,例如修改内存映射或控制外设。这种限制确保了操作系统对资源的完全控制。

  • 监督模式(Supervisor Mode):在监督模式下,CPU可以执行所有指令,并访问系统中的所有资源。操作系统的内核运行在监督模式中,确保它可以管理内存、硬盘、设备驱动等关键资源。

4. 如何从用户模式切换到监督模式?

  • 中断(Interrupt)和陷阱(Trap):程序不能直接切换到监督模式。通常,当程序需要访问系统资源时,会通过触发中断陷阱进入监督模式。操作系统会处理这些中断,并决定是否允许程序执行所请求的操作。

  • 例如,当程序需要访问硬盘上的文件时,它不能直接操作硬盘,而是通过系统调用(syscall)请求操作系统。操作系统会进入监督模式,执行所需的操作,然后返回给程序。

  • 特殊指令:有些情况下,程序可以通过使用特殊指令退出监督模式,回到用户模式。例如,当操作系统初始化硬件并准备运行用户级程序时,会使用这些特殊指令。

5. 监督模式的重要性

  • 保护系统稳定性:如果应用程序能够随意进入监督模式并修改系统资源,任何小错误都有可能导致整个系统崩溃。这就是为什么大多数操作系统代码和应用程序通常运行在用户模式下,只有少数关键部分需要使用监督模式。

  • 防止系统崩溃:在监督模式中执行的代码如果出现错误,通常会导致严重的系统问题,例如**蓝屏(Blue Screen of Death)**或磁盘损坏。这些错误往往是致命的,因此操作系统必须非常谨慎地管理进入监督模式的情况。

6. 监督模式与超级用户(Superuser)

  • 监督模式有点像操作系统中的“超级用户”权限。超级用户可以访问系统的所有资源,并执行普通用户无法执行的操作。类似地,监督模式赋予操作系统内核完全控制硬件的能力。

  • 不过,监督模式比超级用户的使用更为谨慎,因为错误的使用可能导致系统崩溃甚至硬件损坏。

系统调用(Syscall)

1. 什么是系统调用(Syscall)?

  • 系统调用是用户模式的应用程序请求操作系统执行某些操作的一种机制。例如,程序想要读取文件、创建新进程、请求更多内存或发送网络数据时,无法直接操作这些资源,而是通过系统调用将请求传递给操作系统。

  • 操作系统在监督模式下运行,拥有对硬件资源的完全控制。用户模式的程序必须通过系统调用来请求操作系统执行这些敏感操作。

2. 常见的系统调用示例

  • 读取文件:程序通过系统调用请求操作系统从磁盘上读取数据。例如,操作系统控制文件系统,确保程序只能访问允许的文件。

  • 启动新进程:通过系统调用,应用程序可以请求操作系统创建一个新的进程,以并行运行另一个程序。

  • 请求更多内存:当程序需要分配新的内存空间(如使用malloc函数),它会通过系统调用请求操作系统为其分配额外的内存。

  • 发送数据:通过系统调用,程序可以请求操作系统将数据通过网络接口发送出去,操作系统负责管理网络资源。

3. 如何执行系统调用?

执行系统调用的步骤通常如下:

  1. 设置参数:程序首先将请求的操作所需的参数设置到寄存器中。例如:
  • 读取文件时,需要传递文件的路径和要读取的字节数等信息。

  • 分配内存时,需要传递请求的内存大小。

  1. 触发软件中断:程序接着会执行一个特殊的汇编指令,触发软件中断。这个中断告诉CPU要从用户模式切换到监督模式,由操作系统接管控制权。
  • 不同的CPU架构有不同的指令来触发中断。例如,在一些架构中,这个指令叫做syscall,在其他架构中可能叫做trap或int。
  1. 操作系统处理请求:操作系统接收到中断信号后,进入监督模式,根据传递的参数来执行相应的操作。例如:
  • 如果程序请求读取文件,操作系统会访问文件系统并读取指定的数据。

  • 如果程序请求创建新进程,操作系统会分配新的内存空间并启动新进程。

  1. 返回用户模式:操作系统执行完请求后,会将结果返回给程序,并切换回用户模式。程序可以继续执行,并处理返回的结果。

4. 为什么需要系统调用?

  • 安全性:系统调用机制确保了用户程序不能直接访问和修改关键的系统资源,如文件、内存、硬件设备等。通过让操作系统作为中介,程序只能通过受控的方式访问这些资源,确保了系统的安全性。

  • 资源管理:操作系统通过系统调用管理系统资源的分配。例如,操作系统可以根据程序的需求分配内存或管理文件访问权限,避免多个程序争抢同一资源。

  • 稳定性:如果没有系统调用,用户程序可以直接操作硬件资源,容易导致冲突或崩溃。通过系统调用,操作系统可以控制和调度资源的使用,确保系统的稳定运行。

5. 系统调用的工作流程

  • 程序想要访问某些受保护的资源时,会设置相关的参数并触发系统调用。

  • 操作系统接管请求,在监督模式下处理请求。

  • 操作完成后,操作系统将控制权还给程序,并返回相应的结果。

中断和异常(Interrupts, Exceptions)

当以下事件发生时,我们就会切进监督模式

1. 中断(Interrupt)

  • 中断是由外部事件触发的,通常来自正在运行的程序之外的世界。中断可以在任何时候发生,它让操作系统暂时中断当前的任务,去处理外部的请求。

  • 例子包括:

  • 键盘输入:当你按下键盘上的一个键,系统会产生一个中断,操作系统中断当前运行的程序,去处理键盘输入。

  • 网络数据传输:当有新的网络数据到达时,也会触发中断,通知操作系统处理这些数据。

  • 计时器中断:操作系统通过计时器控制任务的时间分片,当计时器到时会触发中断,操作系统切换到另一个任务。

  • 中断是异步发生的,意味着它可以在任何指令执行的间隙发生。

2. 异常(Exception)

  • 异常是由程序运行过程中发生的错误或特殊情况触发的,它是同步的,发生在指令执行的过程中。异常通常是因为程序试图执行非法操作或违反了权限规定。

  • 常见的异常例子包括:

  • 非法内存访问:程序尝试访问它不应该访问的内存地址,比如访问另一个进程的内存,操作系统会捕捉这个异常并进行处理。

  • 非法指令:程序尝试执行不存在或不允许的指令(例如在用户模式下执行监督模式指令)。

  • 访问CSR寄存器:程序尝试读取或写入它无权访问的控制和状态寄存器(CSR),会触发异常。

3. 系统调用与ECALL

  • ECALL(Environment Call)是一种特殊的指令,用户程序通过它向操作系统发送请求。ECALL会触发异常,并进入更高的权限级别(即监督模式)。这是**系统调用(syscall)的实现方式之一。

  • 例如,当程序需要读取文件、分配内存或启动新进程时,它会执行一个ECALL指令,将控制权交给操作系统,让操作系统处理这些请求。

  • ECALL相当于程序与操作系统通信的桥梁,操作系统通过处理这些请求来管理资源和硬件。

4. EBREAK

  • EBREAK是一条特殊的指令,通常用于调试。它会在程序当前的权限级别(例如用户模式)中触发异常。EBREAK让程序暂时停止执行,并触发一个断点(breakpoint),以便开发人员可以调试程序。

  • EBREAK通常用于设置程序中的断点,调试器可以捕获这个指令,检查程序的状态,帮助开发人员找出问题。

陷阱处理程序(Trap Handler)

Pasted image 20240921171740.png

1. Trap Handler的机器状态视角

  • Trap handler(陷阱处理程序)的视角是:被陷阱触发的指令(例如内存错误)之前的所有指令都已完成,而陷阱之后的指令还未执行。

  • 这意味着处理程序可以通过恢复用户寄存器并跳转回被中断的指令来返回。

  • 换句话说,陷阱处理程序不需要理解处理器的流水线状态,或者程序在陷阱发生时具体做了什么,只需恢复上下文并继续执行。

2. 中断和异常

  • 中断处理相对简单,因为它通常不会改变当前的程序状态。它可以通过保存当前寄存器状态并在处理中断后恢复,继续执行程序。

  • **异常(trap)**处理更加复杂,因为它通常与程序的执行逻辑有关,比如非法操作或内存错误。在异常发生时,处理器必须保存当前状态,以便能够正确恢复和继续执行。

3. 精确陷阱(Precise Traps)

  • 超标量乱序执行(Superscalar Out-of-Order Execution)的流水线处理器中,提供精确陷阱是非常困难的。原因是处理器可以同时执行多条指令,且这些指令并非按顺序完成。

  • 精确陷阱的要求是:每次发生陷阱时,处理器必须能够保证之前所有指令已完成,而之后的指令未执行。尽管这在复杂处理器中非常棘手,但为了让系统正常工作,必须实现这一点。

五级流水线中异常的例子

Pasted image 20240921174000.png

处理异常也是一种执行一些硬件不支持的指令的方式。比如硬件不支持乘法,但是我们可以将乘法运算交给Trap Handler来处理,让软件来算,虽然这样会慢一点,但是不会导致机器崩溃。

陷阱处理(Trap Handling)

1. 像处理流水线危险一样处理异常

  • 异常的处理方式与流水线中的数据冒险控制冒险类似。为了保证程序执行的正确性,处理器必须采取一些步骤来解决异常,确保系统状态的一致性。

2. 处理异常的步骤

  1. 完成在异常发生前的指令
  • 处理器在处理异常时,首先必须确保在异常发生之前的所有指令都已经完全执行并完成其对系统状态的修改。这样可以保证在异常发生时,处理器的状态是一致的,不会受到影响。
  1. 清空流水线中的指令
  • 异常发生时,处理器流水线中可能还有指令正在等待执行。为了确保异常处理的正确性,处理器会清空流水线中尚未执行的指令。

  • 这些指令通常会被转换为NOPs(No Operation,空操作)或者气泡,这样就不会影响系统状态。

  1. 可选地将异常原因存储在状态寄存器中
  • 在某些情况下,处理器会将引发异常的原因存储在状态寄存器中,这样操作系统可以知道是哪种异常发生了。

  • 状态寄存器中会指示异常的类型,例如非法指令、内存访问错误等。这对于异常处理程序了解错误的性质并采取正确的措施非常重要。

  1. 转移控制权到陷阱处理程序
  • 处理器完成上述步骤后,会将执行权转移到陷阱处理程序(Trap Handler)。陷阱处理程序是操作系统的一部分,负责处理异常并做出响应。

  • 在陷阱处理程序中,操作系统可能会尝试修复错误,或是终止引发异常的程序。

  1. 可选地返回并重新执行指令
  • 在某些情况下,异常可以通过操作系统的处理被修复。例如,如果异常是由缺页错误(page fault)引发的,操作系统可以加载缺失的页面并恢复程序的执行。

  • 当错误被修复后,陷阱处理程序可以返回原始程序,并重新执行引发异常的指令。这一步是可选的,具体取决于异常的类型和处理方式。

多任务处理(Multiprogramming)

  • 定义:操作系统可以同时运行多个应用程序,这种机制称为多任务处理。通过快速切换,操作系统可以让用户感觉多个程序是同时运行的,即使实际上它们可能不是在同一时刻执行。

  • 单核 vs 多核:如果你的计算机只有一个CPU核心,则同时只能真正运行一个进程。但是操作系统可以非常快速地在多个进程之间切换,让用户觉得这些进程是并行运行的。如果每个进程分配到一个核心(在多核系统上),那么它们可以真正并行运行。

上下文切换(Context Switch)

  • 操作系统通过一种称为上下文切换的机制,在不同的进程之间快速切换。

  • 过程

  1. 当切换到一个新的进程时,操作系统会设置一个计时器

  2. 当计时器到期时,处理器会触发一个中断,告诉操作系统该切换到另一个进程了。

  3. 在切换进程之前,操作系统会保存当前进程的状态,包括程序计数器(PC)和寄存器的内容。

  4. 然后,操作系统加载另一个进程的状态,并将控制权转移给这个新的进程。

  5. 最后,操作系统重新设置计时器,进入用户模式,并跳转到新进程的程序计数器位置继续执行。

调度(Scheduling)

  • 操作系统在决定哪个进程应该被执行时,会使用调度算法。调度算法根据进程的优先级、时间片等规则来选择哪个进程应该运行。

  • 调度的目标是确保所有进程都能公平地获得CPU时间,同时尽量提高系统性能。

保护、地址转换和分页

  • 问题:仅靠**监督模式(Supervisor Mode)**不能完全隔离不同的应用程序。例如,一个程序可能会意外或恶意地访问并修改另一个程序的内存,这会导致数据损坏。程序通常会从某个固定地址开始运行,如果多个程序使用相同的地址(如0x8FFFFFFF),它们的内存会发生冲突。

虚拟内存(Virtual Memory)

  • 解决方案:操作系统通过虚拟内存机制来解决这些问题。

  • 虚拟内存给每个进程提供了一个独立的虚拟地址空间,使得每个进程以为它拥有整个内存的控制权。

  • 事实上,操作系统会将虚拟地址映射到物理内存中的实际地址。这样,多个程序可以同时运行,而它们的内存不会发生冲突。

  • 地址转换:每个进程的虚拟地址通过地址转换机制(通常由内存管理单元,MMU,处理)映射到物理地址。每个进程有自己独立的映射表(页表),因此它们的虚拟地址空间是独立的。

  • 扩展内存:虚拟内存还能提供比实际物理内存更大的地址空间。当物理内存不足时,操作系统会将不常用的内存页面移动到硬盘(称为分页(paging)),并在需要时将其调回内存。这让程序能够使用远超过物理内存容量的地址空间,解决了稀疏数据结构等问题。

评论

  1. test
    6 月前
    2024-9-23 17:28:50

    测试

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇