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部署安装Nginx

    docker部署安装Nginx 前言 Nginx是一个高性能的HTTP和反向代理web服务器,同事也提供了IMAP/POP3/SMTP服务。特点: 轻量级的Web服务器/反向代理服…

    Linux 2023年6月6日
    098
  • CentOS下配置NTP时间服务器

    配置ntp.conf [root@server ~]# vim /etc/ntp.conf For more information about this file, see th…

    Linux 2023年6月13日
    0114
  • 哨兵+redis主从部署架构-docker部署

    架构图 哨兵的介绍 sentinel , 中文是哨兵。 哨兵是redis 集群架构中非常重要的一个组件,主要功能如下: (1)集群监控:负责监控reidis master 和sla…

    Linux 2023年5月28日
    091
  • Question05-查询所有同学的学生编号、学生姓名、选课总数、所有课程的总成绩

    * SELECT a.SID, a.Sname, COUNT(b.CID) 选课总数, SUM(score) 总成绩 FROM Student a , SC b WHERE a.S…

    Linux 2023年6月7日
    0145
  • LVS+KeepAlived高可用部署架构

    1 构建高可用集群 1.1 什么是高可用集群 高可用集群(High Availability Cluster,简称HA Cluster),是指以减少服务中断时间为目的得服务器集群技…

    Linux 2023年6月13日
    086
  • Xshell+Xftp SSH隧道代理

    参考:https://blog.csdn.net/firetreesf/article/details/53287633 Original: https://www.cnblogs…

    Linux 2023年5月28日
    088
  • ETCD分布式存储部署

    一、ETCD 概述 ETCD 是一个分布式一致性k-v存储系统,可用于服务注册发现与共享配置。具有一下优点: 简单: 相比于晦涩难懂的paxos算法,etcd基于相对简单且易实现的…

    Linux 2023年6月14日
    0104
  • dbus的奇妙世界

    故事背景 在linux开发中我们经常会用到dbus来进行进程间通信,但是如何理解dbus服务端和客户端呢?很多小伙伴可能都会遇到类似的问题,而且都是含含糊糊的,接下来我们直接上硬菜…

    Linux 2023年5月27日
    090
  • VMware 和 Linux 的安装

    常见的虚拟机软件有 VMware Workstation(简称 VMware)、VirtualBox、Microsoft Virtual PC 等,本文以 VMware 为例来讲解…

    Linux 2023年5月27日
    090
  • Linux 用户密码不能设置问题

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月7日
    0130
  • 剑指offer计划18( 搜索与回溯算法中等)—java

    1.1、题目1 剑指 Offer 55 – II. 平衡二叉树 1.2、解法 递归和下一面一题的结合版,abs去绝对值判断两边的差,然后递归isBalanced来遍历二…

    Linux 2023年6月11日
    065
  • docker save与docker export实现docker镜像与容器的备份

    本来想写一篇关于docker save/export/commit/load/import之间的关系的文章,后来看了看,已经有很多人写过了,我就不做重复工作了。 参见: docke…

    Linux 2023年6月6日
    0111
  • 教你搞懂Jenkins安装部署!

    前言:请各大网友尊重本人原创知识分享,谨记本人博客: 南国以南i Jenkins介绍 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作…

    Linux 2023年6月14日
    0130
  • DOS-批处理隐藏自身窗口

    批处理隐藏运行效果代码,防止出现黑窗口不建议非法用途,可以用来执行命令,提供用户体验。 运行bat时隐藏cmd窗口的方法 运行bat时隐藏cmd窗口的方法 可以编辑一个vbs脚本,…

    Linux 2023年6月8日
    0115
  • JavaScript事件处理(三)

    上机三 JavaScript事件处理 目的: 熟练掌握JavaScript事件处理机制 重点理解面向对象编程思想,并构建程序。 要求: 定义一个按钮,动态生成DIV,可以生成多个D…

    Linux 2023年6月13日
    098
  • 一篇文章学会shell脚本

    一、Shell传递参数 运行: 二、Shell数组 运行: 三、Shell运算符 1、算术运算符 注意:条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的…

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