嵌入式汇编+系统调用

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表示无效。

原文地址:https://www.cnblogs.com/cdwodm/p/2753334.html