如何在独立环境下关闭计算机?

15

我正在基于英特尔x86架构制作一个保护模式操作系统,想了解如何通过汇编代码或类似方法关闭计算机。您能帮助我解决这个问题吗?

可以使用汇编指令`ACPI`(高级配置和电源接口)来关闭计算机。具体而言,您可以在汇编代码中使用以下语句:
``` mov eax, 0x2000 mov ebx, 0x0001 int 0x15 ```
这将调用BIOS中的ACPI函数,然后关机。

3
http://osdev.org 是一个不错的网站...虽然我在自己的业余操作系统中从未成功运行过关机代码,所以我无法给出一个好的答案。 - Earlz
1
可能是使用汇编语言关闭计算机的重复问题或非常相似的问题。 - Preet Sangha
2
@Preet,相关但我认为这不是一个完全相同的问题。这个问题问如何从自己的OS(或独立环境)关闭它,而另一个问题并不假设这一点。@Carlos,你处于哪种处理器模式?实模式、保护模式还是长模式?(16位、32位还是64位) - Earlz
4
“关机”和“断电”是两种不同的事情。关机很简单,只需终止所有进程,并将内核置于可以拔掉电源而不会损坏任何东西的状态(想象一下“现在可以关闭电脑”的屏幕)。然后,关机基本上就是关闭并发送ACPI Poweroff信号。 - Anon.
2
这个论坛帖子有你需要的内容吗?http://forum.osdev.org/viewtopic.php?t=16990 - mbauman
2个回答

10

来自 http://forum.osdev.org/viewtopic.php?t=16990

ACPI关机技术上非常简单,只需要outw(PM1a_CNT, SLP_TYPa | SLP_EN);计算机就会关闭电源。

问题在于这些值的获取,特别是SLP_TYPa在_DSDT中的_S5对象中,因此需要进行AML编码。

下面是一个简单的“映射”,用于查找这些字段。

    "RSD PTR "
      ||
    RsdtAddress指针位于偏移量16处
      ||
      \/
    "RSDT"
      ||
    偏移量36 + 4 * n处的指针(检查目标是否为“FACP”以获得正确的n)
      ||
      \/
    "FACP"
      ||
      ||=====\
      ||   ||
      ||   PM1a_CNT_BLK; 偏移量:64(参见第4.7.3.2节)
      ||   PM1b_CNT_BLK; 偏移量:68
      ||      ||
      ||      \/
      ||      SLP_TYPx; 位10-12
      ||      SLP_EN;     位13
      ||
    DSDT指针位于偏移量40处
      ||
      \/
    "DSDT"   (以某种方式导出\_S5对象。)

要导出\_S5对象,通常需要使用AML解释器,但考虑到我们正在构建一款业余操作系统,这显然不是一个选项。简单的解决方案是手动扫描DSDT。AML语言规定,_...对象只定义一次,因此很容易找到\_S5对象,因为简单的memcmp()就足够了。一旦找到,SLP_TYPx值就会被提取。
    \_S5对象的字节码
    -----------------------------------------
            |(可选)| |  |  |  |
    NameOP | \          | _  | S  | 5  | _
    08     | 5A         | 5F | 53 | 35 | 5F
----------------------------------------------------------------------------------------------------------- | | |(SLP_TYPa)|(SLP_TYPb)|(保留)|(保留) PackageOP | PkgLength | NumElements | byteprefix Num | byteprefix Num | byteprefix Num | byteprefix Num 12 | 0A | 04 | 0A 05 | 0A 05 | 0A 05 | 0A 05
----这个结构也被看到过---------------------- PackageOP | PkgLength | NumElements | 12 | 06 | 04 | 00 00 00 00
最好在操作系统初始化时收集信息,因为在此之后,您可以重用内存而不必担心破坏它。
现在,你只需要执行outw(PM1a_CNT, SLP_TYPa | SLP_EN );,然后你就离开了。 如果PM1b_CNT != 0,则需要使用b重复它。
如果这有点抽象,这里有一些代码可以看看。
//
// here is the slighlty complicated ACPI poweroff code
//

#include <stddef.h>
#include <print.h>
#include <string.h>
#include <io.h>
#include <time.h>



dword *SMI_CMD;
byte ACPI_ENABLE;
byte ACPI_DISABLE;
dword *PM1a_CNT;
dword *PM1b_CNT;
word SLP_TYPa;
word SLP_TYPb;
word SLP_EN;
word SCI_EN;
byte PM1_CNT_LEN;



struct RSDPtr
{
   byte Signature[8];
   byte CheckSum;
   byte OemID[6];
   byte Revision;
   dword *RsdtAddress;
};



struct FACP
{
   byte Signature[4];
   dword Length;
   byte unneded1[40 - 8];
   dword *DSDT;
   byte unneded2[48 - 44];
   dword *SMI_CMD;
   byte ACPI_ENABLE;
   byte ACPI_DISABLE;
   byte unneded3[64 - 54];
   dword *PM1a_CNT_BLK;
   dword *PM1b_CNT_BLK;
   byte unneded4[89 - 72];
   byte PM1_CNT_LEN;
};



// check if the given address has a valid header
unsigned int *acpiCheckRSDPtr(unsigned int *ptr)
{
   char *sig = "RSD PTR ";
   struct RSDPtr *rsdp = (struct RSDPtr *) ptr;
   byte *bptr;
   byte check = 0;
   int i;

   if (memcmp(sig, rsdp, 8) == 0)
   {
      // check checksum rsdpd
      bptr = (byte *) ptr;
      for (i=0; i<sizeof(struct RSDPtr); i++)
      {
         check += *bptr;
         bptr++;
      }

      // found valid rsdpd   
      if (check == 0) {
         /*
          if (desc->Revision == 0)
            wrstr("acpi 1");
         else
            wrstr("acpi 2");
         */
         return (unsigned int *) rsdp->RsdtAddress;
      }
   }

   return NULL;
}



// finds the acpi header and returns the address of the rsdt
unsigned int *acpiGetRSDPtr(void)
{
   unsigned int *addr;
   unsigned int *rsdp;

   // search below the 1mb mark for RSDP signature
   for (addr = (unsigned int *) 0x000E0000; (int) addr<0x00100000; addr += 0x10/sizeof(addr))
   {
      rsdp = acpiCheckRSDPtr(addr);
      if (rsdp != NULL)
         return rsdp;
   }


   // at address 0x40:0x0E is the RM segment of the ebda
   int ebda = *((short *) 0x40E);   // get pointer
   ebda = ebda*0x10 &0x000FFFFF;   // transform segment into linear address

   // search Extended BIOS Data Area for the Root System Description Pointer signature
   for (addr = (unsigned int *) ebda; (int) addr<ebda+1024; addr+= 0x10/sizeof(addr))
   {
      rsdp = acpiCheckRSDPtr(addr);
      if (rsdp != NULL)
         return rsdp;
   }

   return NULL;
}



// checks for a given header and validates checksum
int acpiCheckHeader(unsigned int *ptr, char *sig)
{
   if (memcmp(ptr, sig, 4) == 0)
   {
      char *checkPtr = (char *) ptr;
      int len = *(ptr + 1);
      char check = 0;
      while (0<len--)
      {
         check += *checkPtr;
         checkPtr++;
      }
      if (check == 0)
         return 0;
   }
   return -1;
}



int acpiEnable(void)
{
   // check if acpi is enabled
   if ( (inw((unsigned int) PM1a_CNT) &SCI_EN) == 0 )
   {
      // check if acpi can be enabled
      if (SMI_CMD != 0 && ACPI_ENABLE != 0)
      {
         outb((unsigned int) SMI_CMD, ACPI_ENABLE); // send acpi enable command
         // give 3 seconds time to enable acpi
         int i;
         for (i=0; i<300; i++ )
         {
            if ( (inw((unsigned int) PM1a_CNT) &SCI_EN) == 1 )
               break;
            sleep(10);
         }
         if (PM1b_CNT != 0)
            for (; i<300; i++ )
            {
               if ( (inw((unsigned int) PM1b_CNT) &SCI_EN) == 1 )
                  break;
               sleep(10);
            }
         if (i<300) {
            wrstr("enabled acpi.\n");
            return 0;
         } else {
            wrstr("couldn't enable acpi.\n");
            return -1;
         }
      } else {
         wrstr("no known way to enable acpi.\n");
         return -1;
      }
   } else {
      //wrstr("acpi was already enabled.\n");
      return 0;
   }
}

//
// bytecode of the \_S5 object
// -----------------------------------------
//        | (optional) |    |    |    |   
// NameOP | \          | _  | S  | 5  | _
// 08     | 5A         | 5F | 53 | 35 | 5F
//
// -----------------------------------------------------------------------------------------------------------
//           |           |              | ( SLP_TYPa   ) | ( SLP_TYPb   ) | ( Reserved   ) | (Reserved    )
// PackageOP | PkgLength | NumElements  | byteprefix Num | byteprefix Num | byteprefix Num | byteprefix Num
// 12        | 0A        | 04           | 0A         05  | 0A          05 | 0A         05  | 0A         05
//
//----this-structure-was-also-seen----------------------
// PackageOP | PkgLength | NumElements |
// 12        | 06        | 04          | 00 00 00 00
//
// (Pkglength bit 6-7 encode additional PkgLength bytes [shouldn't be the case here])
//
int initAcpi(void)
{
   unsigned int *ptr = acpiGetRSDPtr();

   // check if address is correct  ( if acpi is available on this pc )
   if (ptr != NULL && acpiCheckHeader(ptr, "RSDT") == 0)
   {
      // the RSDT contains an unknown number of pointers to acpi tables
      int entrys = *(ptr + 1);
      entrys = (entrys-36) /4;
      ptr += 36/4;   // skip header information

      while (0<entrys--)
      {
         // check if the desired table is reached
         if (acpiCheckHeader((unsigned int *) *ptr, "FACP") == 0)
         {
            entrys = -2;
            struct FACP *facp = (struct FACP *) *ptr;
            if (acpiCheckHeader((unsigned int *) facp->DSDT, "DSDT") == 0)
            {
               // search the \_S5 package in the DSDT
               char *S5Addr = (char *) facp->DSDT +36; // skip header
               int dsdtLength = *(facp->DSDT+1) -36;
               while (0 < dsdtLength--)
               {
                  if ( memcmp(S5Addr, "_S5_", 4) == 0)
                     break;
                  S5Addr++;
               }
               // check if \_S5 was found
               if (dsdtLength > 0)
               {
                  // check for valid AML structure
                  if ( ( *(S5Addr-1) == 0x08 || ( *(S5Addr-2) == 0x08 && *(S5Addr-1) == '\\') ) && *(S5Addr+4) == 0x12 )
                  {
                     S5Addr += 5;
                     S5Addr += ((*S5Addr &0xC0)>>6) +2;   // calculate PkgLength size

                     if (*S5Addr == 0x0A)
                        S5Addr++;   // skip byteprefix
                     SLP_TYPa = *(S5Addr)<<10;
                     S5Addr++;

                     if (*S5Addr == 0x0A)
                        S5Addr++;   // skip byteprefix
                     SLP_TYPb = *(S5Addr)<<10;

                     SMI_CMD = facp->SMI_CMD;

                     ACPI_ENABLE = facp->ACPI_ENABLE;
                     ACPI_DISABLE = facp->ACPI_DISABLE;

                     PM1a_CNT = facp->PM1a_CNT_BLK;
                     PM1b_CNT = facp->PM1b_CNT_BLK;

                     PM1_CNT_LEN = facp->PM1_CNT_LEN;

                     SLP_EN = 1<<13;
                     SCI_EN = 1;

                     return 0;
                  } else {
                     wrstr("\\_S5 parse error.\n");
                  }
               } else {
                  wrstr("\\_S5 not present.\n");
               }
            } else {
               wrstr("DSDT invalid.\n");
            }
         }
         ptr++;
      }
      wrstr("no valid FACP present.\n");
   } else {
      wrstr("no acpi.\n");
   }

   return -1;
}



void acpiPowerOff(void)
{
   // SCI_EN is set to 1 if acpi shutdown is possible
   if (SCI_EN == 0)
      return;

   acpiEnable();

   // send the shutdown command
   outw((unsigned int) PM1a_CNT, SLP_TYPa | SLP_EN );
   if ( PM1b_CNT != 0 )
      outw((unsigned int) PM1b_CNT, SLP_TYPb | SLP_EN );

   wrstr("acpi poweroff failed.\n");
}

如需更多信息,请阅读ACPI 1.0a规范的相应部分

    9.1.7   从工作状态转换到软关机状态
    7.5.2   \_Sx 状态
    7.4.1   \_S5
    4.7.2.3    睡眠/唤醒控制
16.3 AML字节流字节值 16.2.3 包长度编码

这在我的所有机器上都可以使用bochs和qemu。 但我注意到电脑不需要启用ACPI就可以关机。虽然我不知道这是否总是这样。

如果您只想玩一下。 对于bochs和qemu,它是outw( 0xB004, 0x0 | 0x2000 );


1

APM

https://en.wikipedia.org/wiki/Advanced_Power_Management

在Ubuntu 14.04上测试了qemu-system-i386 2.0.0的方法:

mov $0x5301, %ax
xor %bx, %bx
int $0x15

/* Try to set apm version (to 1.2). */
mov $0x530e, %ax
xor %bx, %bx
mov $0x0102, %cx
int $0x15

/* Turn off the system. */
mov $0x5307, %ax
mov $0x0001, %bx
mov $0x0003, %cx
int $0x15

关于在QEMU上编译和运行的确切步骤,请参阅此存储库

osdev.org文章:http://wiki.osdev.org/Shutdownhttp://wiki.osdev.org/APM

ACPI是更新、更好的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接