kernel源码(九)main.c

0 __asm__

参考1:https://www.cnblogs.com/zhenjingcool/p/15925494.html中的嵌入式汇编部分

参考2:https://blog.csdn.net/yt_42370304/article/details/84982864

00 系统调用int 0x80

在保护模式下,内核采用中断的方式实现系统调用,中断向量号为0x80;这是操作系统在用户态访问内核态唯一的途径。

具体哪种系统调用,是由eax中的值决定的,称为功能号,功能号在include/linux/sys.h中定义的

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid };

这里定义了一个数组,数组中的元素是相关系统调用函数地址,数组下标就是功能号,比如我们调用int 0x80时eax中存放2的话,就是sys_fork系统调用。

系统调用的一般过程

1、用户空间的某一个函数,比如我们在c程序写的系统应用中需要创建一个进程,我们会调用c语言API的fork()函数

2、c中的fork()函数中会执行汇编命令int 0x80发起系统调用,因为执行了int 0x80,因而从用户态转到内核态。

3、系统调用int 0x80本质上是一个软中断,其对应一个中断处理程序,这个中断处理程序是system_call.s中的_system_call标号。(为什么中断处理程序是system_call.s后面解释)

4、_system_call中做现场保护,然后根据eax中的系统调用号,查找_sys_call_table中对应的系统调用。并调用sys_fork函数。

为什么int 0x80中断处理程序是system_call.s?下面进行解释

在main.c中执行初始化时,其中有一步是sched_init();,这个函数在sched.c中定义的

void sched_init(void)
{
    int i;
    struct desc_struct * p;

    if (sizeof(struct sigaction) != 16)
        panic("Struct sigaction MUST be 16 bytes");
    set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
    set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
    p = gdt+2+FIRST_TSS_ENTRY;
    for(i=1;i<NR_TASKS;i++) {
        task[i] = NULL;
        p->a=p->b=0;
        p++;
        p->a=p->b=0;
        p++;
    }
/* Clear NT, so that we won't have troubles with that later on */
    __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
    ltr(0);
    lldt(0);
    outb_p(0x36,0x43);        /* binary, mode 3, LSB/MSB, ch 0 */
    outb_p(LATCH & 0xff , 0x40);    /* LSB */
    outb(LATCH >> 8 , 0x40);    /* MSB */
    set_intr_gate(0x20,&timer_interrupt);
    outb(inb_p(0x21)&~0x01,0x21);
    set_system_gate(0x80,&system_call);
}

最下面一行set_system_gate(0x80,&system_call);,在这里设置了中断号0x80对应的中断处理程序是system_call。

1 源码

/*
 *  linux/init/main.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__
#include <unistd.h>
#include <time.h>

/*
 * we need this inline - forking from kernel space will result
 * in NO COPY ON WRITE (!!!), until an execve is executed. This
 * is no problem, but for the stack. This is handled by not letting
 * main() use the stack at all after fork(). Thus, no function
 * calls - which means inline code for fork too, as otherwise we
 * would use the stack upon exit from 'fork()'.
 *
 * Actually only pause and fork are needed inline, so that there
 * won't be any messing with the stack from main(), but we define
 * some others too.
 */
static inline _syscall0(int,fork)
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)

#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <asm/system.h>
#include <asm/io.h>

#include <stddef.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

#include <linux/fs.h>

static char printbuf[1024];

extern int vsprintf();
extern void init(void);
extern void blk_dev_init(void);
extern void chr_dev_init(void);
extern void hd_init(void);
extern void floppy_init(void);
extern void mem_init(long start, long end);
extern long rd_init(long mem_start, int length);
extern long kernel_mktime(struct tm * tm);
extern long startup_time;

/*
 * This is set up by the setup-routine at boot-time
 */
#define EXT_MEM_K (*(unsigned short *)0x90002)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)

/*
 * Yeah, yeah, it's ugly, but I cannot find how to do this correctly
 * and this seems to work. I anybody has more info on the real-time
 * clock I'd be interested. Most of this was trial and error, and some
 * bios-listing reading. Urghh.
 */

#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})

#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)

static void time_init(void)
{
    struct tm time;

    do {
        time.tm_sec = CMOS_READ(0);
        time.tm_min = CMOS_READ(2);
        time.tm_hour = CMOS_READ(4);
        time.tm_mday = CMOS_READ(7);
        time.tm_mon = CMOS_READ(8);
        time.tm_year = CMOS_READ(9);
    } while (time.tm_sec != CMOS_READ(0));
    BCD_TO_BIN(time.tm_sec);
    BCD_TO_BIN(time.tm_min);
    BCD_TO_BIN(time.tm_hour);
    BCD_TO_BIN(time.tm_mday);
    BCD_TO_BIN(time.tm_mon);
    BCD_TO_BIN(time.tm_year);
    time.tm_mon--;
    startup_time = kernel_mktime(&time);
}

static long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;

struct drive_info { char dummy[32]; } drive_info;

void main(void)        /* This really IS void, no error here. */
{            /* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
     ROOT_DEV = ORIG_ROOT_DEV;
     drive_info = DRIVE_INFO;
    memory_end = (1<<20) + (EXT_MEM_K<<10);
    memory_end &= 0xfffff000;
    if (memory_end > 16*1024*1024)
        memory_end = 16*1024*1024;
    if (memory_end > 12*1024*1024) 
        buffer_memory_end = 4*1024*1024;
    else if (memory_end > 6*1024*1024)
        buffer_memory_end = 2*1024*1024;
    else
        buffer_memory_end = 1*1024*1024;
    main_memory_start = buffer_memory_end;
#ifdef RAMDISK
    main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
    mem_init(main_memory_start,memory_end);
    trap_init();
    blk_dev_init();
    chr_dev_init();
    tty_init();
    time_init();
    sched_init();
    buffer_init(buffer_memory_end);
    hd_init();
    floppy_init();
    sti();
    move_to_user_mode();
    if (!fork()) {        /* we count on this going ok */
        init();
    }
/*
 *   NOTE!!   For any other task 'pause()' would mean we have to get a
 * signal to awaken, but task0 is the sole exception (see 'schedule()')
 * as task 0 gets activated at every idle moment (when no other tasks
 * can run). For task0 'pause()' just means we go check if some other
 * task can run, and if not we return here.
 */
    for(;;) pause();
}

static int printf(const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    write(1,printbuf,i=vsprintf(printbuf, fmt, args));
    va_end(args);
    return i;
}

static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", NULL };

static char * argv[] = { "-/bin/sh",NULL };
static char * envp[] = { "HOME=/usr/root", NULL };

void init(void)
{
    int pid,i;

    setup((void *) &drive_info);
    (void) open("/dev/tty0",O_RDWR,0);
    (void) dup(0);
    (void) dup(0);
    printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
        NR_BUFFERS*BLOCK_SIZE);
    printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
    if (!(pid=fork())) {
        close(0);
        if (open("/etc/rc",O_RDONLY,0))
            _exit(1);
        execve("/bin/sh",argv_rc,envp_rc);
        _exit(2);
    }
    if (pid>0)
        while (pid != wait(&i))
            /* nothing */;
    while (1) {
        if ((pid=fork())<0) {
            printf("Fork failed in init\r\n");
            continue;
        }
        if (!pid) {
            close(0);close(1);close(2);
            setsid();
            (void) open("/dev/tty0",O_RDWR,0);
            (void) dup(0);
            (void) dup(0);
            _exit(execve("/bin/sh",argv,envp));
        }
        while (1)
            if (pid == wait(&i))
                break;
        printf("\n\rchild %d died with code %04x\n\r",pid,i);
        sync();
    }
    _exit(0);    /* NOTE! _exit, not exit() */
}
View Code

开头

#define __LIBRARY__
#include <unistd.h>
#include <time.h>

接下来,定义了4个内联函数,而且是在unistd.h中的宏定义。

static inline _syscall0(int,fork)
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)

我们看一下unistd.h中是如何定义_syscall0的

#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name)); \ //##为C中的语法,这里表示连接__NR_和name,这里是__NR_fork,这个是在本头文件中定义的宏(#define __NR_fork 2)
if (__res >= 0) \
    return (type) __res; \
errno = -__res; \
return -1; \
}

在这个宏定义中,嵌入了汇编代码。

int 0x80:int表示中断,0x80表示系统调用,int 0x80表示发起一个系统调用

 "0" (__NR_##name) 为asm的输入,__NR_##name为2,这里意思是把2放入0的位置处,即把2放入eax寄存器中(asm规定,0到9分别表示特定的位置,不明白可以查看:https://blog.csdn.net/yt_42370304/article/details/84982864)

 "=a" (__res) 为asm的输出,表示把eax中的内容写到变量__res中。

上面这段代码表示发起一个系统调用,功能号是2,功能号2对应的是fork系统调用。

我们再回过头来看main.c中的这段代码

static inline _syscall0(int,fork)
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)

这里定义了4个内联函数,作用分别是系统调用fork、系统调用pause、系统调用setup、系统调用sync。需要注意的是,本文件中定义了这4个系统调用,但是没有在本文件中使用。

下面引入了一些头文件

#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <asm/system.h>
#include <asm/io.h>

#include <stddef.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

#include <linux/fs.h>

下面定义了一个char数组

 static char printbuf[1024]; 

下面是一些函数,他们的原型分别在不同的c文件中。

extern int vsprintf();
extern void init(void);
extern void blk_dev_init(void);
extern void chr_dev_init(void);
extern void hd_init(void);
extern void floppy_init(void);
extern void mem_init(long start, long end);
extern long rd_init(long mem_start, int length);
extern long kernel_mktime(struct tm * tm);
extern long startup_time;

我们在setup.s中在0x90000开始的位置放入了一些数据(https://www.cnblogs.com/zhenjingcool/p/15944047.html),这里我们开始使用这些数据

/*
 * This is set up by the setup-routine at boot-time
 */
#define EXT_MEM_K (*(unsigned short *)0x90002) //0x90002处存放的是扩展内存大小。这里我们把扩展内存大小赋值给宏EXT_MEM_K
#define DRIVE_INFO (*(struct drive_info *)0x90080) //第一个硬盘的信息
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC) //根设备信息

下面代码是读取cmos实时时钟信息。

#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \ //0x70是写端口号,0x80|addr 是要读取的CMOS 内存地址
inb_p(0x71); \ //0x71 是读端口号。
})

outb_p是一个宏,在io.h中定义,汇编中outb指令是向特定io端口写一字节数据。inb是特定io端口向源头读入1字节数据。

#define outb(value,port) \
__asm__ ("outb %%al,%%dx"::"a" (value),"d" (port)) //无输出,输入:value写入eax,port写入edx。这个嵌入汇编意思是把value发到port端口


#define inb(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx,%%al":"=a" (_v):"d" (port)); \ //从port端口读取1字节数据放入eax,并把eax值存入_v;返回_v
_v; \
})

#define outb_p(value,port) \
__asm__ ("outb %%al,%%dx\n" \
        "\tjmp 1f\n" \ //jmp 1f意思是跳转到标号1处,方向是向前跳转(因为这里有两个标号1,所以要区分向前还是向后)
        "1:\tjmp 1f\n" \
        "1:"::"a" (value),"d" (port))

#define inb_p(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx,%%al\n" \
    "\tjmp 1f\n" \
    "1:\tjmp 1f\n" \
    "1:":"=a" (_v):"d" (port)); \
_v; \
})

下面代码定义了一个宏,作用是把BCD码转换为二进制的值

 #define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10) 

下面代码实现时间初始化。

static void time_init(void)
{
    struct tm time; //在time.h中定义的

    do {
        time.tm_sec = CMOS_READ(0); //读取cmos时间,秒.在教材7.1.3.1小节有相关介绍,cmos在指定位置存放了当前时分秒。通过0x70设置地址,cmos会把读取后的信息放到0x71,。我们读取0x71就获取到值
        time.tm_min = CMOS_READ(2); //分钟
        time.tm_hour = CMOS_READ(4); //小时
        time.tm_mday = CMOS_READ(7); //天
        time.tm_mon = CMOS_READ(8); //月
        time.tm_year = CMOS_READ(9); //年
    } while (time.tm_sec != CMOS_READ(0)); //如果读取时间超过1秒,则重新读取。保证误差在1秒之内
    BCD_TO_BIN(time.tm_sec);//cmos中的值是bcd码,这里对bcd码进行转码
    BCD_TO_BIN(time.tm_min);
    BCD_TO_BIN(time.tm_hour);
    BCD_TO_BIN(time.tm_mday);
    BCD_TO_BIN(time.tm_mon);
    BCD_TO_BIN(time.tm_year);
    time.tm_mon--; //因为tm_mon从1-12,这里做减1操作
    startup_time = kernel_mktime(&time); //创建时间,赋值给start_time
}

定义了一个结构体

 struct drive_info { char dummy[32]; } drive_info; 

下面是main函数,在介绍head.s时我们讲过,head.s中设置了调用c中main函数的入口,就是指的这里(注意:汇编调用c中的函数时要加_,即_main)

下面的代码需要对照这个图查看脉络将会更清晰。

系统内存划分为内核程序,缓存,虚拟盘,主存储区这几部分,见下图

void main(void)        /* This really IS void, no error here. */
{            /* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then 此时还处于关中断状态,关中断是在setup.s中关闭的,从那以后到目前为止还没有打开。
 * enable them
 */
     ROOT_DEV = ORIG_ROOT_DEV; //ROOT_DEV是在其他文件中定义的宏,讲到时我们再回来看这地方
     drive_info = DRIVE_INFO; //DRIVE_INFO前面定义的宏,这里赋值给driver_info
    memory_end = (1<<20) + (EXT_MEM_K<<10); //计算总的内存大小,单位为字节,1左移20位表示1M基本内存地址,EXT_MEM_K是前面定义的宏表示扩展内存大小(kb),这里左移10位转换为字节
    memory_end &= 0xfffff000; //因为内存是分页的,1页是4kb,这里把最后不足1页的内存去掉
    if (memory_end > 16*1024*1024) //如果内存大于16M,
        memory_end = 16*1024*1024;
    if (memory_end > 12*1024*1024) //如果内存大于12M,缓冲区大小设置为4M
        buffer_memory_end = 4*1024*1024;
    else if (memory_end > 6*1024*1024) //如果内存大于6M,缓冲区大小设置为2M
        buffer_memory_end = 2*1024*1024;
    else
        buffer_memory_end = 1*1024*1024;
    main_memory_start = buffer_memory_end; //缓冲区后面是主内存的地址
#ifdef RAMDISK //如果定义了虚拟内存,则主内存开始位置还要往后延一点
    main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
    mem_init(main_memory_start,memory_end); //初始化主内存
    trap_init(); //中断门初始化
    blk_dev_init(); //块设备初始化
    chr_dev_init(); //字符设备初始化
    tty_init(); //tty的初始化
    time_init(); //时间的初始化
    sched_init(); //调度程序的初始化,这里会初始化任务0。因为调度程序依赖于时间片中断,在中断打开之前是不会发生调度的,任务0会等待开中断后由调度程序调度执行?解释:在执行了下面的sti()函数开中断后,接下来将由调度程序决定CPU上下文执行哪个进程,此时只有一个进程:任务0,因此此时任务0开始执行。
    buffer_init(buffer_memory_end); //缓冲区管理的初始化
    hd_init();//硬盘初始化
    floppy_init();//软盘初始化
    sti();//打开中断
    move_to_user_mode();//在system.h中定义的宏,作用是初始化数据段、附加段。任务0是如何运行的?通过在堆栈中设置的参数,利用中断返回指令启动任务0执行
    if (!fork()) {        //fork是前面定义的宏,作用是新建子进程,这里创建的是任务1。fork()调用会产生和父进程一样的进程描述符,也就是说父进程和子进程的代码段完全一样,执行相同的代码,对于父进程来说,fork()调用返回进程号pid,对于子进程来说,fork()调用返回0.因此这里if判断条件决定了父进程和子进程不同的代码执行逻辑。此处 子进程中if条件才为真,执行init()函数,父进程不会执行init()函数。
        init();//在init中会初始化标准输入stdin、标准输出stdout。然后重新打开一个sh交互程序,也就是我们看到的那个黑框,只要我们不exit,黑框一直存在,也就是这个进程n一直运行,当我们关闭了黑框或者执行exit命令,这个进程n才结束。当然我们可以打开多个交互式sh窗口,也就是打开多个sh进程。
    }
/*
 *   NOTE!!   For any other task 'pause()' would mean we have to get a
 * signal to awaken, but task0 is the sole exception (see 'schedule()')
 * as task 0 gets activated at every idle moment (when no other tasks
 * can run). For task0 'pause()' just means we go check if some other
 * task can run, and if not we return here.
 */
    for(;;) pause(); //这一段的意思是,定义了一个死循环,每循环一次,执行一次pause,如果此时有其他进程在等待,将获取cpu时间片执行;如果没有其他进程,将一直执行for循环;这里就是那个idel进程。运行到这,说明
}

其中下面这些初始化是非常重要的部分,后面博客会一一讲到。

    mem_init(main_memory_start,memory_end); //初始化主内存
    trap_init(); //中断门初始化
    blk_dev_init(); //块设备初始化
    chr_dev_init(); //字符设备初始化
    tty_init(); //tty的初始化
    time_init(); //时间的初始化
    sched_init(); //调度程序的初始化,这里会创建任务0,并且移动到任务0中执行
    buffer_init(buffer_memory_end); //缓冲区管理的初始化
    hd_init();//硬盘初始化
    floppy_init();//软盘初始化

下面程序,创建任务1,并执行init函数

    if (!fork()) {        //fork是前面定义的宏,作用是新建子进程
        init();
    }

定义了一些字符数组作为sh程序的参数,后面会用到

static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", NULL };

static char * argv[] = { "-/bin/sh",NULL };
static char * envp[] = { "HOME=/usr/root", NULL };

我们看一下init()函数。

init函数有如下3个功能:1 安装根文件系统;2 运行系统初始资源配置文件/etc/rc中的命令;3 执行用户登陆shell程序

void init(void)
{
    int pid,i;

    setup((void *) &drive_info); //设置硬盘,在hd.c中定义
    (void) open("/dev/tty0",O_RDWR,0); //以读写的方式打开/dev/tty0。它对应终端控制台。由于这是第一次打开文件操作,因此产生的文件描述符(文件句柄)是0,也就是标准输入句柄stdin
    (void) dup(0); //复制文件描述符,产生1号句柄,即stdout标准输出设备
    (void) dup(0); //复制文件描述符,产生2号句柄,即stderr标准错误输出设备
    printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
        NR_BUFFERS*BLOCK_SIZE);
    printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
    if (!(pid=fork())) { //再创建一个子进程(任务2),同样的道理,对于父进程fork()函数返回pid,对于子进程fork()函数返回0;此处只有子进程(也就是任务2)会进入if分支
        close(0);//关闭文件描述符0,并立即打开文件/etc/rc,从而把标准输入stdin定向到/etc/rc文件上。这样所有的标准输入数据都将从该文件中读取。
        if (open("/etc/rc",O_RDONLY,0))//打开/etc/rc文件
            _exit(1);
        execve("/bin/sh",argv_rc,envp_rc);//内核以非交互形式执行/bin/sh,从而实现执行/etc/rc文件中的命令,当该文件中的命令执行完毕后,/bin/sh就会立即退出,因此进程2也就随之结束。
        _exit(2); //退出子进程
    }
    if (pid>0) //如果子进程创建成功,这里是父进程(任务1)执行的分支,只有任务1才能进入if里面
        while (pid != wait(&i)) //等待子进程(任务2)结束,任务2以非交互方式运行sh程序,执行/etc/rc里面的命令,运行完则任务2结束
            /* nothing */;
    while (1) { //如果子进程(任务2)结束,才轮到这里执行
        if ((pid=fork())<0) { //再创建一个进程
            printf("Fork failed in init\r\n");
            continue;
        }
        if (!pid) { //子进程才会执行这个if分支。在子进程中关闭
            close(0);close(1);close(2);//在这个新创建的子进程中,关闭所有以前遗留下来的句柄(stdin,stdout,stderr)
            setsid();//新创建一个会话
            (void) open("/dev/tty0",O_RDWR,0);//重新打开/dev/tty0作为stdin
            (void) dup(0);//stdout
            (void) dup(0);//stderr
            _exit(execve("/bin/sh",argv,envp));//再次执行/bin/sh程序,但这次执行所传递的参数和前面不一样,参数中包含了-/bin/sh,注意前面的横线-,表示登录shell,和前面非交互式shell不同。这个登录shell将一直运行,除非我们执行exit命令退出。
        }
        while (1) //这里父进程while循环判断某一子进程是否退出,如果退出则打印"XXX退出"等信息,然后继续等待其他登录shell(子进程)退出。此外wait()函数也处理孤儿进程,把其父进程号改为1号进程。
            if (pid == wait(&i))
                break;
        printf("\n\rchild %d died with code %04x\n\r",pid,i);
        sync(); //刷新缓冲区
    }
    _exit(0);    /* NOTE! _exit, not exit() */
}

我们对main.c程序讲解了一个框架,里面一些细节我们会在接下来的文章中逐步深化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
au3反编译源码 myAut2Exe - The Open Source AutoIT Script Decompiler 2.9 ======================================================== *New* full support for AutoIT v3.2.6++ :) ... mmh here's what I merely missed in the 'public sources 3.1.0' This program is for studying the 'Compiled' AutoIt3 format. AutoHotKey was developed from AutoIT and so scripts are nearly the same. Drag the compiled *.exe or *.a3x into the AutoIT Script Decompiler textbox. To copy text or to enlarge the log window double click on it. Supported Obfuscators: 'Jos van der Zande AutoIt3 Source Obfuscator v1.0.14 [June 16, 2007]' , 'Jos van der Zande AutoIt3 Source Obfuscator v1.0.15 [July 1, 2007]' , 'Jos van der Zande AutoIt3 Source Obfuscator v1.0.20 [Sept 8, 2007]' , 'Jos van der Zande AutoIt3 Source Obfuscator v1.0.22 [Oct 18, 2007]' , 'Jos van der Zande AutoIt3 Source Obfuscator v1.0.24 [Feb 15, 2008]' , 'EncodeIt 2.0' and 'Chr() string encode' Tested with: AutoIT : v3. 3. 0.0 and AutoIT : v2.64. 0.0 and AutoHotKey: v1.0.48.5 The options: =========== 'Force Old Script Type' Grey means auto detect and is the best in most cases. However if auto detection fails or is fooled through modification try to enable/disable this setting 'Don't delete temp files (compressed script)' this will keep *.pak files you may try to unpack manually with'LZSS.exe' as well as *.tok DeTokeniser files, tidy backups and *.tbl (<-Used in van Zande obfucation). If enable it will keep AHK-Scripts as they are and doesn't remove the linebreaks at the beginning Default:OFF 'Verbose LogOutput' When checked you get verbose information when decompiling(DeTokenise) new 3.2.6+ compiled Exe Default:OFF 'Restore Includes' will separated/restore includes. requires ';<AUT2EXE INCLUDE-START' comment to be present in the script to work Default:ON 'Use 'normal' Au3_Signature to find start of script' Will uses the normal 16-byte start signature to detect the start of a
深入分析Linux内核源码 前言 第一章 走进linux 1.1 GNU与Linux的成长 1.2 Linux的开发模式和运作机制 1.3走进Linux内核 1.3.1 Linux内核的特征 1.3.2 Linux内核版本的变化 1.4 分析Linux内核的意义 1.4.1 开发适合自己的操作系统 1.4.2 开发高水平软件 1.4.3 有助于计算机科学的教学和科研 1.5 Linux内核结构 1.5.1 Linux内核在整个操系统中的位置 1.5.2 Linux内核的作用 1.5.3 Linux内核的抽象结构 1.6 Linux内核源代码 1.6.1 多版本的内核源代码 1.6.2 Linux内核源代码的结构 1.6.3 从何处开始阅读源代码 1.7 Linux内核源代码分析工具 1.7.1 Linux超文本交叉代码检索工具 1.7.2 Windows平台下的源代码阅读工具Source Insight 第二章 Linux运行的硬件基础 2.1 i386的寄存器 2.1.1通用寄存器 2.1.2段寄存器 2.1.3状态和控制寄存器 2.1.4 系统地址寄存器 2.1.5 调试寄存器和测试寄存器 2.2 内存地址 2.3 段机制和描述符 2.3.1 段机制 2.3.2 描述符的概念 2.3.3系统段描述符 2.3.4 描述符表 2.3.5 选择符与描述符表寄存器 2.3.6 描述符投影寄存器 2.3.7 Linux中的段 2.4 分页机制 2.4.1 分页机构 2.4.2页面高速缓存 2.5 Linux中的分页机制 2.5.1 与页相关的数据结构及宏的定义 2.5.2 对页目录及页表的处理 2.6 Linux中的汇编语言 2.6.1 AT&T与Intel汇编语言的比较 2.6.2 AT&T汇编语言的相关知识 2.6.3 Gcc嵌入式汇编 2.6.4 Intel386汇编指令摘要 第三章中断机制 3.1 中断基本知识 3.1.1 中断向量 3.1.2 外设可屏蔽中断 3.1.3异常及非屏蔽中断 3.1.4中断描述符表 3.1.5 相关汇编指令 3.2中断描述符表的初始化 3.2. 1 外部中断向量的设置 3.2.2中断描述符表IDT的预初始化 3.2.3 中断向量表的最终初始化 3.3异常处理 3.3.1 在内核栈中保存寄存器的值 3.3.2 中断请求队列的初始化 3.3.3中断请求队列的数据结构 3.4 中断处理 3.4.1中断和异常处理的硬件处理 3.4.2 Linux对异常和中断的处理 3.4.3 与堆栈有关的常量、数据结构及宏 3.4.4 中断处理程序的执行 3.4.5 从中断返回 3.5中断的后半部分处理机制 3.5.1 为什么把中断分为两部分来处理 3.5.2 实现机制 3.5.3数据结构的定义 3.5.4 软中断、bh及tasklet的初始化 3.5.5后半部分的执行 3.5.6 把bh移植到tasklet 第四章 进程描述 4.1 进程和程序(Process and Program) 4.2 Linux中的进程概述 4.3 task_struct结构描述 4.4 task_struct结构在内存中的存放 4.4.1 进程内核栈 4.4.2 当前进程(current宏) 4.5 进程组织的方式 4.5.1哈希表 4.5.2双向循环链表 4.5.3 运行队列 4.5.4 等待队列 4.6 内核线程 4.7 进程的权能 4.8 内核同步 4.8.1信号量 4.8.2原子操作 4.8.3 自旋锁、读写自旋锁和大读者自旋锁 4.9 本章小节 第五章进程调度 5.1 Linux时间系统 5.1.1 时钟硬件 5.1.2 时钟运作机制 5.1.3 Linux时间基准 5.1.4 Linux的时间系统 5.2 时钟中断 5.2.1 时钟中断的产生 5.2.2.Linux实现时钟中断的全过程 5.3 Linux的调度程序-Schedule( ) 5.3.1 基本原理 5.3.2 Linux进程调度时机 5.3.3 进程调度的依据 5.3.4 进程可运行程度的衡量 5.3.5 进程调度的实现 5.4 进程切换 5.4.1 硬件支持 5.4.2 进程切换 第六章 Linux内存管理 6.1 Linux的内存管理概述 6.1.1 Linux虚拟内存的实现结构 6.1.2 内核空间和用户空间 6.1.3 虚拟内存实现机制间的关系 6.2 Linux内存管理的初始化 6.2.1 启用分页机制 6.2.2 物理内存的探测 6.2.3 物理内存的描述 6.2.4 页面管理机制的初步建立 6.2.5页表的建立 6.2.6内存管理区 6.3 内存的分配和回收 6.3.1 伙伴算法 6.3.2 物理页面的分配和释放 6.3.3 Slab分配机制 6.4 地址映射机制 6.4.1 描述虚拟空间的数据结构 6.4.2 进程的虚拟空间 6.4.3 内存映射 6.5 请页机制 6.5.1 页故障的产生 6.5.2 页错误的定位 6.5.3 进程地址空间中的缺页异常处理 6.5.4 请求调页 6.5.5 写时复制 6.6 交换机制 6.6.1 交换的基本原理 6.6.2 页面交换守护进程kswapd 6.6.3 交换空间的数据结构 6.6.4 交换空间的应用 6.7 缓存和刷新机制 6.7.1 Linux使用的缓存 6.7.2 缓冲区高速缓存 6.7.3 翻译后援存储器(TLB) 6.7.4 刷新机制 6.8 进程的创建和执行 6.8.1 进程的创建 6.8.2 程序执行 6.8.3 执行函数 第七章 进程间通信 7.1 管道 7.1.1 Linux管道的实现机制 7.1.2 管道的应用 7.1.3 命名管道(FIFO) 7.2 信号(signal) 7.2.1 信号种类 7.2.2 信号掩码 7.2.3 系统调用 7.2.4 典型系统调用的实现 7.2.5 进程与信号的关系 7.2.6 信号举例 7.3 System V 的IPC机制 7.3.1 信号量 7.3.2 消息队列 7.3.3 共享内存 第八章 虚拟文件系统 8.1 概述 8.2 VFS中的数据结构 8.2.1 超级块 8.2.2 VFS的索引节点 8.2.3 目录项对象 8.2.4 与进程相关的文件结构 8.2.5 主要数据结构间的关系 8.2.6 有关操作的数据结构 8.3 高速缓存 8.3.1 块高速缓存 8.3.2 索引节点高速缓存 8.3.3 目录高速缓存 8.4 文件系统的注册、安装与拆卸 8.4.1 文件系统的注册 8.4.2 文件系统的安装 8.4.3 文件系统的卸载 8.5 限额机制 8.6 具体文件系统举例 8.6.1 管道文件系统pipefs 8.6.2 磁盘文件系统BFS 8.7 文件系统的系统调用 8.7.1 open 系统调用 8.7.2 read 系统调用 8.7.3 fcntl 系统调用 8 .8 Linux2.4文件系统的移植问题 第章 Ext2文件系统 9.1 基本概念 9.2 Ext2的磁盘布局和数据结构 9.2.1 Ext2的磁盘布局 9.2.2 Ext2的超级块 9.2.3 Ext2的索引节点 9.2.4 组描述符 9.2.5 位图 9.2.6 索引节点表及实例分析 9.2.7 Ext2的目录项及文件的定位 9.3 文件的访问权限和安全 9.4 链接文件 9.5 分配策略 9.5.1 数据块寻址 9.5.2 文件的洞 9.5.3 分配一个数据块 第十章 模块机制 10.1 概述 10.1.1 什么是模块 10.1.2 为什么要使用模块? 10.2 实现机制 10.2.1 数据结构 10.2.2 实现机制的分析 10.3 模块的装入和卸载 10.3.1 实现机制 10.3.2 如何插入和卸载模块 10.4 内核版本 10.4.1 内核版本与模块版本的兼容性 10.4.2 从版本2.0到2.2内核API的变化 10.4.3 把内核2.2移植到内核2.4 10.5 编写内核模块 10.5.1 简单内核模块的编写 10.5.2 内核模块的Makefiles文件 10.5.3 内核模块的多个文件 第十一章 设备驱动程序 11.1 概述 11.1.1 I/O软件 11.1.2 设备驱动程序 11.2 设备驱动基础 11.2.1 I/O端口 11.2.2 I/O接口及设备控制器 11.2.3 设备文件 11.2.4 VFS对设备文件的处理 11.2.5 中断处理 11.2.6 驱动DMA工作 11.2.7 I/O 空间的映射 11.2.8 设备驱动程序框架 11.3 块设备驱动程序 11.3.1 块设备驱动程序的注册 11.3.2 块设备基于缓冲区的数据交换 11.3.3 块设备驱动程序的几个函数 11.3.4 RAM 盘驱动程序的实现 11.3.5 硬盘驱动程序的实现 11.4 字符设备驱动程序 11.4.1 简单字符设备驱动程序 11.4.2 字符设备驱动程序的注册 11.4.3 一个字符设备驱动程序的实例 11.4.4 驱动程序的编译与装载 第十二章 网络 12.1 概述 12.2 网络协议 12.2.1 网络参考模型 12.2.2 TCP/IP 协议工作原理及数据流 12.2.3 Internet 协议 12.2.4 TCP协议 12.3 套接字(socket) 12.3.1 套接字在网络中的地位和作用 12.3.2 套接字接口的种类 12.3.3 套接字的工作原理 12.3.4 socket 的通信过程 12.3.5 socket为用户提供的系统调用 12.4 套接字缓冲区(sk_buff) 12.4.1 套接字缓冲区的特点 12.4.2 套接字缓冲区操作基本原理 12.4.3 sk_buff数据结构的核心内容 12.4.4 套接字缓冲区提供的函数 12.4.5 套接字缓冲区的上层支持例程 12.5 网络设备接口 12.5.1 基本结构 12.5.2 命名规则 12.5.3 设备注册 12.5.4 网络设备数据结构 12.5.5 支持函数 第十三章 启动系统 13.1 初始化流程 13.1.1 系统加电或复位 13.1.2 BIOS启动 13.1.3 Boot Loader 13.1.4 操作系统的初始化 13.2 初始化的任务 13.2.1 处理器对初始化的影响 13.2.2 其他硬件设备对处理器的影响 13.3 Linux 的Boot Loarder 13.3.1 软盘的结构 13.3.2 硬盘的结构 13.3.3 Boot Loader 13.3.4 LILO 13.3.5 LILO的运行分析 13.4 进入操作系统 13.4.1 Setup.S 13.4.2 Head.S 13.5 main.c中的初始化 13.6 建立init进程 13.6.1 init进程的建立 13.6.2 启动所需的Shell脚本文件 附录: 1 Linux 2.4内核API 2.1 驱动程序的基本函数 2.2 双向循环链表的操作 2.3 基本C库函数 2.4 Linux内存管理中Slab缓冲区 2.5 Linux中的VFS 2.6 Linux的连网 2.7 网络设备支持 2.8 模块支持 2.9 硬件接口 2.10 块设备 2.11 USB 设备 2 参考文献
\初级驱动\drivers_实验资料\drivers_实验资料\kernel课程\内核课程.chm 内核源码下载和结构; 目录结构: Arch: 架构:arm x86 ppc mips :Arch/arm/ lib:存放体系结构特有的对诸如strlen和memcpy之类的通用函数的实现。与arch/*/lib下的代码不同,这里的库代码都是使用C编写的,在内核新的移植版本中可以直接使用。arch/*/lib就是跟具体架构相关的函数,大部分都是汇编代码 mm:存放体系结构特有的内存管理程序的实现。 Drivers:这个目录是内核中最庞大的一个目录,显卡、网卡、SCSI适配器、PCI总线、USB总线和其他任何Linux支持的外围设备或总线的驱动程序都可以在这里找到,驱动一般分成字符设备和块设备,但是现在内核把驱动做成了很多子系统,比如input子系统,mtd子系统,mmc子系统,media子系统等等 Init:内核的启动初始化代码。包括main.c、创建早期用户空间的代码以及其他初始化代码。 Kernel:内核中最核心的部分,包括进程的调度(kernel/sched.c),以及进程的创建和撤销(kernel/fork.c和kernel/exit.c)等,和平台相关的另外一部分核心的代码在arch/*/kernel目录。 Crypto:内核本身所用的加密API,实现了常用的加密和散列算法,还有一些压缩和CRC校验算法。 Usr:实现了用于打包和压缩的的cpio等 Virt:允许使用其他操作系统,然后在宿主 Linux 内核的用户空间中运行内核中的 KVM ,通过/dev/kvm字符设备来公开虚拟化后的硬件 Documentation:非常有用的目录,内核文档,可以帮助我们理解内核中的一些技术 fs: 文件系统的代码,包含Linux常见的文件系统格式,比如ext3,ext4,cramfs,jffs2,如果想让内核支持更多的文件系统格式,那么可以专注到这个目录 ipc:进程间通信的实现代码,比如共享内存,消息队列,信号量,主要是system V的,其他的就没有了 net: 大部分的网络协议代码就在这里了,比如IPv4,IPv6,tcp,udp,ieee80211,域套接字

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值