第三部分,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,完成摇身一变
核心逻辑就是 加载文件、 调整内存、 开始执行三个步骤
一部分一部分看吧
- 第一部分
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 格式了,但大体的思想是一致的。
- 第四部分
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二头部,其实就是把这两个字符串存入了进去
- 第六部分
#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;
}
1. 第八部分
.....
eip[0] = ex.a_entry; /* eip, magic happens :-) */
eip[3] = p; /* stack pointer */
return 0;
.....
Original: https://www.cnblogs.com/shuisanya/p/16615346.html
Author: 水三丫
Title: linux-0.11分析:进程初始化函数init(),第三部分,fork创建第二个进程,第十四篇随笔
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/581458/
转载文章受原作者版权保护。转载请注明原作者出处!