linux-0.11分析:进程初始化函数init(),第三部分,fork创建第二个进程,第十四篇随笔

第三部分,fork创建第二个进程

[引用github这个博主 多多支持][ https://github.com/sunym1993/flash-linux0.11-talk ]
先看看 init中的这段代码

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

看见了 pid=fork()又在创建进程了开启 int 0x80中断,去执行 sys_fork

这个进程创建结束了

调用了一个 close(0)函数

lib文件 -> close.c

_syscall1(int,close,int,fd)

fs文件 -> open.c

int sys_close(unsigned int fd)
{
    struct file * filp;

    if (fd >= NR_OPEN)
        return -EINVAL;
    current->close_on_exec &= ~(1<filp[fd]))
        return -EINVAL;
    current->filp[fd] = NULL;
    if (filp->f_count == 0)
        panic("Close: file count is 0");
    if (--filp->f_count)
        return (0);
    iput(filp->f_inode);
    return (0);
}

close(0) 就是 关闭 0 号文件描述符,也就是进程 1 复制过来的打开了 tty0 并作为标准输入的文件描述符,那么此时 0 号文件描述符就空出来了。

然后就是打开了一个 /etc/rc文件

open("/etc/rc",O_RDONLY,0)

将0号文件描述符指向了这个 /etc/rc

然后再执行了一个重要的函数 execve("/bin/sh",argv_rc,envp_rc);

execve("/bin/sh",argv_rc,envp_rc);

include文件 -> execve.c

_syscall3(int,execve,const char *,file,char **,argv,char **,envp)

kernel文件 -> system_call.s

EIP = 0x1C
_sys_execve:
    lea EIP(%esp),%eax
    pushl %eax
    call _do_execve
    addl $4,%esp
    ret

这里调用了 do_execve

fs文件 -> exec.c

#define MAX_ARG_PAGES 32
int do_execve(unsigned long * eip,
              long tmp,
              char * filename,
              char ** argv,
              char ** envp)
{
    struct m_inode * inode;
    struct buffer_head * bh;
    struct exec ex;
    unsigned long page[MAX_ARG_PAGES];
    int i,argc,envc;
    int e_uid, e_gid;
    int retval;
    int sh_bang = 0;
    unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;
    ......

}

这个文件相对比较长,就用省略号代替了

[引用github这个博主 多多支持][ https://github.com/sunym1993/flash-linux0.11-talk ]

eip 调用方触发系统调用时由 CPU 压入栈空间中的 eip 的指针 。
tmp 是一个无用的占位参数。
filename 是 “/bin/sh”
argv 是 { “/bin/sh”, NULL }
envp

执行步骤

1 检查文件类型和权限等
2 读取文件的第一块数据到缓冲区
3 脚本文件与可执行文件的判断
4 校验可执行文件是否能执行
5 进程管理结构的调整
6 释放进程占有的页面
7 调整线性地址空间、参数列表、堆栈地址等
8 设置 eip 和 esp,完成摇身一变
核心逻辑就是 加载文件调整内存开始执行三个步骤

一部分一部分看吧

  1. 第一部分
static int count(char ** argv)
{
    int i=0;
    char ** tmp;

    if (tmp = argv)
        while (get_fs_long((unsigned long *) (tmp++)))
            i++;

    return i;
}
......

for (i=0 ; i

先初始化了 page[32],全部赋值为0 然后读取了 filename这个文件, inode=namei(filename),filename是 “/bin/sh” 然后把这两个参数: argv 是 { “/bin/sh”, NULL }, envp 是 { “HOME=/”, NULL },转化为了int类型
2. 第二部分

.....

e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
if (current->euid == inode->i_uid)
    i >>= 6;
else if (current->egid == inode->i_gid)
    i >>= 3;
.....

if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) { //struct buffer_head * bh;
    retval = -EACCES;
    goto exec_error2;
}

前面都在设置一些进程1和进程2的关联,把进程2的文件的i_uid和进程1的euid相关联 看看重要的 bh = bread(inode->i_dev,inode->i_zone[0]读取 filename的第一块文件,也就是1024B=1K
3. 第三部分

......

ex = *((struct exec *) bh->b_data);
......

解析这1KB的数据为exec的结构体 看看这个exec的结构体

include文件 -> a.out.h

struct exec {
 unsigned long a_magic; /* 使用宏N_MAGIC等进行访问 */
 unsigned a_text;       /* 文本长度,以字节为单位 */
 unsigned a_data;       /* 数据长度,以字节为单位 */
 unsigned a_bss;            /* 文件未初始化数据区的长度,以字节为单位 */
 unsigned a_syms;       /* 文件中符号表数据的长度,以字节为单位 */
 unsigned a_entry;      /* 起始地址 */
 unsigned a_trsize;     /* 文本的重新定位信息长度,以字节为单位 */
 unsigned a_drsize;     /* 数据的重新定位信息长度,以字节为单位 */
};

​ 现在的 Linux 已经弃用了这种古老的格式,改用 ELF 格式了,但大体的思想是一致的。

  1. 第四部分
if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) {
    .......

}
brelse(bh);
.....

我们写一个 Linux 脚本文件的时候,通常可以看到前面有这么一坨东西。

#!/bin/sh
#!/usr/bin/python

就是通过判断第一个和第二个元素是否是 # 和 !来判断是否是脚本程序的 我们这里不是脚本程序,所以就不进入这个if了,暂时不需要看了 看下一个 brelse(bh) fs文件 -> buffer.c

void brelse(struct buffer_head * buf)
{
    if (!buf)
        return;
    wait_on_buffer(buf);
    if (!(buf->b_count--))
        panic("Trying to free free buffer");
    wake_up(&buffer_wait);
}

就是把已经解析为exec结构的数据的那个缓存块释放掉,好存储下一个
2. 第五部分

unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;      // 128k -4
......

......          //省略了一部分错误判断
if (!sh_bang) {     //sh_bang = 0
    p = copy_strings(envc,envp,page,p,0);
    p = copy_strings(argc,argv,page,p,0);
    if (!p) {
        retval = -ENOMEM;
        goto exec_error2;
    }
}

主要是执行了 copy_strings这个函数,这里的 argv 是 { “/bin/sh”, NULL }, envp 是 { “HOME=/”, NULL }, 把这两个参数的字符串存放到p指向的这个位置,那么p指向哪里

还记得线性内存地址吗,每个进程占64MB,现在p就指向进程2二头部,其实就是把这两个字符串存入了进去

  1. 第六部分
#define MAX_ARG_PAGES 32
#define PAGE_SIZE 4096
.....

p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
.....

前面做了一些当前线程的修改参数的操作 根据 ex.a_text 修改局部描述符中的 代码段限长 code_limit
2. 第七部分

......

p = (unsigned long) create_tables((char *)p,argc,envc);
......

这里就真正 构造参数表了 先看看这个 create_tables函数

static unsigned long * create_tables(char * p,int argc,int envc)
{
    unsigned long *argv,*envp;
    unsigned long * sp;

    sp = (unsigned long *) (0xfffffffc & (unsigned long) p);
    sp -= envc+1;
    envp = sp;
    sp -= argc+1;
    argv = sp;
    put_fs_long((unsigned long)envp,--sp);
    put_fs_long((unsigned long)argv,--sp);
    put_fs_long((unsigned long)argc,--sp);
    while (argc-->0) {
        put_fs_long((unsigned long) p,argv++);
        while (get_fs_byte(p++)) /* nothing */ ;
    }
    put_fs_long(0,argv);
    while (envc-->0) {
        put_fs_long((unsigned long) p,envp++);
        while (get_fs_byte(p++)) /* nothing */ ;
    }
    put_fs_long(0,envp);
    return sp;
}

linux-0.11分析:进程初始化函数init(),第三部分,fork创建第二个进程,第十四篇随笔
1. 第八部分
.....

eip[0] = ex.a_entry;        /* eip, magic happens :-) */
eip[3] = p;         /* stack pointer */
return 0;
.....

linux-0.11分析:进程初始化函数init(),第三部分,fork创建第二个进程,第十四篇随笔

Original: https://www.cnblogs.com/shuisanya/p/16615346.html
Author: 水三丫
Title: linux-0.11分析:进程初始化函数init(),第三部分,fork创建第二个进程,第十四篇随笔

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/581458/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • docker安装redis

    首先考虑需要安装的redis版本,我这里是安装的redis 6.0.16,如果宿主机没有,那么就docker pull redis:6.0.16 一、指定redis配置文件 我的宿…

    Linux 2023年5月28日
    091
  • 一文教你快速部署OneBlog开源项目

    镜像下载、域名解析、时间同步请点击阿里云开源镜像站 OneBlog是什么? OneBlog,一个简洁美观、功能强大并且自适应的Java博客。使用springboot开发,前端使用B…

    Linux 2023年5月27日
    0110
  • 统计算法_概率基础

    本次有以下函数 1、简单边际概率 2、联合概率 3、条件概率 4、随机变量期望值 5、随机变量方差 6、随机变量协方差 7、联合协方差 8、组合期望回报 9、投资组合风险 说概率前…

    Linux 2023年6月6日
    078
  • clang 分四步编译main.c

    这里用的clang/clang++ 分四步编译main.c/main.cpp文件 1.1 C++源文件 #include int main() { std::cout <&l…

    Linux 2023年6月13日
    069
  • Linux同时输出到管道和标准输出

    想使用Shell脚本对某文本文件中无序的一列数字排序并输出求和结果,文本如下所示: 421350 开头的命令只能输出求和结果,不能同时输出排序结果: [En] The comman…

    Linux 2023年5月27日
    074
  • 2021年3月-第02阶段-前端基础-Flex 伸缩布局-移动WEB开发_flex布局

    移动web开发——flex布局 1.0 传统布局和flex布局对比 1.1 传统布局 兼容性好 布局繁琐 局限性,不能再移动端很好的布局 1.2 flex布局 操作方便,布局极其简…

    Linux 2023年6月8日
    090
  • MySQL双主同步的实现

    双主复制: 在两个节点上都可以写入数据,互为主从节点。 解决单点失败的问题:一个主节点失败,所有节点都会失败。 双主配置: (1) 各节点使用一个惟一server_id (2) 都…

    Linux 2023年6月7日
    083
  • Jenkins+svn自动化部署完整教程

    1、概述 Jenkins 是一个可扩展的持续集成引擎。主要用于持续、自动地构建/测试软件项目、监控一些定时执行的任务。Jenkins用Java语言编写,可在Tomcat等流行的se…

    Linux 2023年6月7日
    092
  • 脚本小子学习–vulnhub靶机DC8

    @ 前言 一、环境搭建 二、目标和思路 三、实际操作 1.信息收集 2.getshell 总结 前言 通过一些靶机实战练习,学习使用现有的工具来成为脚本小子。 一、环境搭建 靶机:…

    Linux 2023年6月7日
    0126
  • Ubuntu20.04桌面版 使用root账号登录

    sodu -i 执行命令,输入当前用户密码,临时切换到root身份 执行passwd命令,修改root登录密码 passwd 修改50-ubuntu.conf文件 执行如下命令: …

    Linux 2023年6月13日
    083
  • webshell查杀的方法

    从您反馈的情况看,是您的网站被植入了webshel后门文件导致的。您可以先对当前的服务器做下快照备份,然后将您的网站代码拷贝到本地进行下webshell查杀:https://www…

    Linux 2023年5月28日
    0102
  • selenium-自动化测试51job网站(MacOS + Safari)2020年10月6日

    登录 51job ,http://www.51job.com 输入搜索关键词 “python”, 地区选择 “杭州”(注意,如果所在…

    Linux 2023年6月14日
    094
  • SQL实战——02. 查找入职员工时间排名倒数第三的员工所有信息

    查找入职员工时间排名倒数第三的员工所有信息CREATE TABLE employees (emp_no int(11) NOT NULL,birth_date date NOT N…

    Linux 2023年6月14日
    093
  • vi/vim编辑器tar 命令

    一开始进入的模式 此模式下,可使用方向键(上、下、左、右键)或 k、j、h、i 移动光标的位置 操作类型 操作键 功能 翻页 Pagedown Pageup 向下翻页 向上翻页 行…

    Linux 2023年6月6日
    071
  • Linux下TIME_WAIT连接优化内核参数tcp_tw_reuse与tcp_tw_recycle区别与联系浅析

    概述 最近学习网络相关知识点,很多文章提到针对TCP time wait(后续简称TW)状态连接进行优化的参数tcp_tw_reuse和tcp_tw_recycle,并且不少文章提…

    Linux 2023年6月6日
    0113
  • Git 命令

    创建仓库 git init 命令创建一个新的 Git 仓库。它用来将已存在但还没有版本控制的项目转换成一个 Git 仓库,或者创建一个空的新仓库。大多数Git命令在未初始化的仓库中…

    Linux 2023年6月8日
    096
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球