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程序写的系统应用中需要创建一个进程,我们会调用glibc为我们提供的库函数fork()。在linux0.11内核中没有库函数,内核为我们提供的fork()函数起到同样的作用

2、glibc中的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函数。

5、系统调用完成后,检查任务结构体中的信号位图,并处理信号

为什么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进程。这一行代码只有任务0才能执行到,任务1进入到上面的if分支中了,也就是说如果没有其他进程,任务0会一直调用pause()
}

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

    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标准输出设备。关于这三行代码的解释,可参考https://blog.csdn.net/m0_53157173/article/details/127784664
    (void) dup(0); //复制文件描述符,产生2号句柄,即stderr标准错误输出设备。最终0号句柄指向/dev/tty0。1,2句柄指向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())) { //再创建一个子进程(任务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程序讲解了一个框架,里面一些细节我们会在接下来的文章中逐步深化。

 

posted @ 2022-03-12 22:49  zhenjingcool  阅读(235)  评论(0编辑  收藏  举报