• 嵌入式汇编+系统调用


    init进程调用的init函数

    1、setup((void*)&drive_info);

    a.setup函数用的是main.c中Line 25的inline _syscall1(int,setup,void *,BIOS),_syscall1()函数调用来自于include/unistd.h中的Line 146

    #define _syscall1(type,name,atype,a) \
    type name(atype a) \
    { \
    long __res; \
    __asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name),"b" ((long)(a))); \
    if (__res >= 0) \
        return (type) __res; \
    10  errno = -__res; \
    11  return -1; \
    12  }

    根据define规则进行替换

    int setup(void* BIOS) \
    { \
    long __res; \
    __asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##setup),"b" ((long)(BIOS))); \
    if (__res >= 0) \
        return (int) __res; \
    errno = -__res; \
    10  return -1; \
    11  }

    b.drive_info

    定义:struct drive_info { char dummy[32]; } drive_info;//drive_info结构其实就是个32位的数组而已

    #define DRIVE_INFO (*(struct drive_info *)0x90080)//定义DRIVE_INFO为0x90080开始的32位的数据。而在setup.s中已经定义好从0x90080开始存放BIOS两个硬盘的参数表,0x90080处存放第一个硬盘的表,0x90090处存放第二个硬盘的表。(表的长度是16位)

    drive_info = DRIVE_INFO;

    因此,setup((void*)&drive_info)的作用就是读取硬盘参数包括分区表信息并加载虚拟盘(若存在的话)和安装根文件系统设备。

    2、(void) open (“/dev/tty0”,O_RDWR,0)

    open函数的定义:

    int open(const char * filename, int flag, ...)
    {
        register int res;
        va_list arg;
     
        va_start(arg,flag);
        __asm__("int $0x80"
            :"=a" (res)
            :"0" (__NR_open),"b" (filename),"c" (flag),
    10          "d" (va_arg(arg,int)));
    11      if (res>=0)
    12          return res;
    13      errno = -res;
    14      return -1;
    15  }
    16   

    功能就是打开设备或者文件。多解释一下他的使用吧

    int open(const char* pathname,int flags,mode_t mode);

    函数说明:

    pathname:字符串,指向欲打开的文件或者设备字符串

    flags:下列参数flags所能使用的标识

    O_RDONLY 以只读方式打开文件
    O_WRONLY 以只写方式打开文件
    O_RDWR 以可读写方式打开文件。上述三种旗标是互斥的,也就是不可同时使用,但可与下列的旗标利用OR(|)运算符组合。
    O_CREAT 若欲打开的文件不存在则自动建立该文件。
    O_EXCL 如果O_CREAT 也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。
    O_NOCTTY 如果欲打开的文件为终端机设备时,则不会将该终端机当成进程控制终端机。
    O_TRUNC 若文件存在并且以可写的方式打开时,此旗标会令文件长度清为0,而原来存于该文件的资料也会消失。
    O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。
    O_NONBLOCK 以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。
    O_NDELAY 同O_NONBLOCK。
    O_SYNC 以同步的方式打开文件。
    O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接,则会令打开文件失败。
    O_DIRECTORY 如果参数pathname 所指的文件并非为一目录,则会令打开文件失败。

    mode_t:仅在创建新文件时使用,用于指定文件的访问权限。因此如果flags没有O_CREAT参数,则第三个参数不起作用,若此时要打开的文件不存在则会报错。

    typedef unsigned short mode_t;

    使用的规则跟Linux中的一样:

    r 4 可读

    w 2 可写

    x 1 可执行

    3、(void) dup(0);

    定义:

    _syscall1(int,dup,int,fd)展开就可以了int dup(int fd)

    由于上一句(void) open(“/dev/tty0”,O_RDWR,0)是第一次打开文件的操作,因此他产生的文件句柄号(文件描述符)肯定是0.然后dup函数的功能就是复制句柄,用于打开不同的标准输出设备。

    4、进程2的创建

    if (!(pid=fork())) {
            close(0);
            if (open("/etc/rc",O_RDONLY,0))
                _exit(1);
            execve("/bin/sh",argv_rc,envp_rc);
            _exit(2);
        }

    创建进程2,对于新创建的子进程,fork函数将会返回0值,因此子进程创建成功后,会进入这个if判断中执行。

    进程2首先关闭句柄0( (void) open (“/dev/tty0”,O_RDWR,0))。然后以只读的方式打开/etc/rc文件。而对于_exit函数:

    volatile void _exit(int exit_code)
    {
        __asm__("int $0x80"::"a" (__NR_exit),"b" (exit_code));
    }

    sys_exit是一个只带一个参数exitcode的函数,该参数就是系统退出(sys_exit)的退出码。_exit通过调用0x80号软中断带参数,系统调用号(_NR_exit,即在sys_call_table中的偏移量)和退出码(exitcode)的方法,来达到调用sys_exit的目的。
    系统调用sys_exit的处理函数定义在文件kernel/exit.c中。
    sys_exit的函数体很简单,只是调用了函数do_exit:
    do_exit((error_code&0xff)<<8);
    (error_code&0xff)<<8的作用就是将error_code的低8位移到高8位中,低8位用0填补,此数将作为参数传给函数do_exit。
    下面来看看函数do_exit是怎样做的。

    int do_exit(long code)
    {
        int i;
     
        free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
        free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
        for (i=0 ; i<NR_TASKS ; i++)
            if (task[i] && task[i]->father == current->pid) {
                task[i]->father = 1;
    10              if (task[i]->state == TASK_ZOMBIE)
    11                  /* assumption task[1] is always init */
    12                  (void) send_sig(SIGCHLD, task[1], 1);
    13          }
    14      for (i=0 ; i<NR_OPEN ; i++)
    15          if (current->filp[i])
    16              sys_close(i);
    17      iput(current->pwd);
    18      current->pwd=NULL;
    19      iput(current->root);
    20      current->root=NULL;
    21      iput(current->executable);
    22      current->executable=NULL;
    23      if (current->leader && current->tty >= 0)
    24          tty_table[current->tty].pgrp = 0;
    25      if (last_task_used_math == current)
    26          last_task_used_math = NULL;
    27      if (current->leader)
    28          kill_session();
    29      current->state = TASK_ZOMBIE;
    30      current->exit_code = code;
    31      tell_father(current->father);
    32      schedule();
    33      return (-1);    /* just to suppress warnings */
    34  }

    首先释放当前进程代码段和数据段所占的内存页。函数free_page_tables()的第一个参数(get_base()返回值)指明在CPU线性地址空间中的起始基地址,第2个参数(get_limit()返回值)说明欲释放的字节长度值。get_base()宏中的current->ldt[1]给出进程代码段描述符的位置,current->ldt[2]给出进程数据段描述符的位置。get_limit()中的0x0f是进程代码段的选择符,0x17是进程数据段的选择符。即在取段基地址时使用该段的段描述符所处地址作为参数,取段长度时使用该段的选择符作为参数。

    查看free_page_tables.

    int free_page_tables(unsigned long from,unsigned long size)
    {
        unsigned long *pg_table;
        unsigned long * dir, nr;
     
        if (from & 0x3fffff)
            panic("free_page_tables called with wrong alignment");//判断线性地址是否在4M的边界上,若不是则显示出错信息,并死机
        if (!from)
            panic("Trying to free up swapper memory space");//判断指定的地址时候=0,若是则显示出错信息"试图释放内核和缓冲区所占的空间
    10      size = (size + 0x3fffff) >> 22;//计算参数size给出的长度所占的页目录项数(因为1个页表管理4M物理内存,所以用右移22位的方式把需要复制的内存长度值除以4M),之所以加上0x3fffff(即4M-1)是为了得到进位整数倍结果,即如果除操作若有余数则size进1,例如,如果原size=4.01M,那么得到的size=2。
    11      dir = (unsigned long *) ((from>>20) & 0xffc); //计算给出的线性基地址所对应的起始目录项的地址。因为每个目录项管理4M内存,所以对应的目录项号(表内偏移)=from>>22(from/4M),而每个目录项占4字节,并且由于页目录表从物理地址0开始存放,因此实际目录项指针=目录项号<<2。综上,线性地址所对应的目录项地址就是from>>20。“与”上0xffc确保目录项指针范围有效(屏蔽掉目录指针的最后两位,这样目录指针只能指向4位为单元的目录项的起始处)
    此时,size是释放的页表页数;dir是起始目录项的指针
    12      for ( ; size-->0 ; dir++) {
    13          if (!(1 & *dir))//判断dir指向的页表是否可用,实际上是判断P位,参见页目录表项结构图。如果P=0,则表示对应的目录项没有使用,继续处理下一个目录项
    14              continue;
    15          pg_table = (unsigned long *) (0xfffff000 & *dir);如果目录项有效,则取出指向的页表地址
    16          for (nr=0 ; nr<1024 ; nr++) {
    17              if (1 & *pg_table)如果该页表项有效则释放对应页
    18                  free_page(0xfffff000 & *pg_table);
    19              *pg_table = 0;页内容清零
    20              pg_table++;指向页表中的下一项
    21          }
    22          free_page(0xfffff000 & *dir);释放该页表所占内存页面
    23          *dir = 0;对应页表的目录项清零
    24      }
    25      invalidate();刷新页变换高速缓冲
    26      return 0;
    27  }

    其中free_page(unsigned long addr)的作用是释放地址addr开始的一页(4k)内存。

    void free_page(unsigned long addr)
    {
        if (addr < LOW_MEM) return;//如果addr小于内存低端1M,则表示在内核程序或高速缓冲中,对此不处理
        if (addr >= HIGH_MEMORY)//如果addr大于物理地址最高端,则显示出错信息
            panic("trying to free nonexistent page");
        addr -= LOW_MEM;
        addr >>= 12;计算要释放内存的启始页面号=(addr-LOW_MEM)/4096
        if (mem_map[addr]) return;//如果该页面号对应的页面映射字节不为0,则减1返回。static unsigned char mem_map [ PAGING_PAGES ] = {0,}; 页面字节映射图,每个页面占用一个字节。为1表示占用,0表示空闲。
        mem_map[addr]=0;//如果该页面号对应的页面映射字节原本就是0,表示该物理页面本来就是空闲的,显示出错信息。
    10      panic("trying to free free page");
    11  }

    高速缓冲刷新函数

    #define invalidate() \
    __asm__("movl %%eax,%%cr3"::"a" (0))这里通过重新加载页目录基地址寄存器cr3的方法达到刷新的目的。之所以进行重新加载就可以刷新,是因为重新加载cr3后TLB就会失效,Intel i386已经设定这样就可以实现刷新


    P位是存在标志。1表示页面有效,0表示无效。

  • 相关阅读:
    什么是数据集
    Fastreport使用经验(转)在Delphi程序中访问报表对象
    多步操作产生错误,请检查每一步的状态值
    删除整个目录
    Win7下虚拟机个人使用小结:Virtual PC,VMware和VirtualBox
    Ribbon_窗体_实现Ribbon风格的窗体
    数学建模python matlab 编程(椭圆声学原理画图证明,解析几何)
    数学建模python matlab 编程(指派问题)
    matlab中如何给一个矩阵中的某几个特定位置赋值
    python matlab 带包实现全排列
  • 原文地址:https://www.cnblogs.com/cdwodm/p/2753334.html
Copyright © 2020-2023  润新知