1. exec.c
exec大概流程是,调用中断陷入内核态后,在内核态内中断处理程序从eax寄存器里发现是调用exec,接着从其他几个寄存器中读出argc,argv,envc等参数,这里有个地方要注意,因为内核态和用户态的段空间是不一样的。linux用fs寄存器存储了用户态的数据段选择符,这样就可以在内核态访问一些用户空间的数据了,还有argv和env都是字符串数组,c语音里字符串是已null结尾的,包括从用户空间读和最后写入新的用户栈内,都大量用到了这个技巧来判断是否是一个字符串的结尾。
还有一个重要的事情,close_on_exec,进程对象有个这个字段,是一个位图,标记了哪些fd是应该在exec后关闭的包括fork后exec这样的情况。很多时候我们不希望子进程占用我们的fd,比如socket,会导致端口一直被占用
do_execve方法主要干了下面这些事
- 参数校验,如果不是从用户栈陷入的调用则报错
- 用nemei方法取执行的文件节点信息,检查文件是否是可执行文件
- 校验进程用户和执行文件的权限
- 读节点的第一块数据
- 如果是#!开头的代表对应的处理脚本文件执行,则解析文本重新构造filename和参数等信息后回到第2步
- 第一块数据现在是执行文件头了,校验一些参数(魔术类型,代码长度,数据长度等)
- 拷贝参数和环境变量到页面
- 释放原来的内存空间
- 重新设置idt描述符的基址和长度
- 把内核栈原来的eip(就是ret的返回点改成执行文件的entry),当从内核态iret后就彻底完成跳转到新程序入口处运行了
2. stat.c
就是处理平时查询文件状态的调用,逻辑都很简单,需要注意的还是当从内核查处的信息是在内核段里的,需要从内核复制到用户空间内。
3. open.c
该文件定义了我们平时使用的cd,chmod,chown等系统调用,最重要的方法应该是sys_open了,前面我们已经知道每个打开个文件都会被加载到一个inode节点,node节点存在inode_table中,是全局的但大小有限制代表着linux系统中同时最多可以被打开的文件数。
而在用户的进程当中,同样也有一个数组叫做file_table存放当前进程当前打开的文件表,每个表项里有一个f_inode指针指向真正的inode节点。这样做的好处是可以让多个进程共享底层同一个文件,而且每个进程都能对访问的文件设置不同的访问权限。比如我们知道的管道pipe,就是多个进程共享一个文件节点,然后写进程只用写权限,读进程只用读权限来实现的。
open主要完成以下事情
- 遍历当前进程的fd数组,找一个空闲的fd
- 复位进程关闭时的句柄位图
- 从file_table中寻找一个未使用的,找到后把fd指向该项
- 调用open_namei打开文件节点
- 初始化一些f的属性,最后返回fd
4. fcntl.c
该文件主要是处理复制文件fd的系统调用,dup,dup2,dup2只是强调复制的fd号(只是如果新fd存在,新关闭它),sys_fcntl主要处理所有的文件控制,根据cmd不同,设置文件的属性,包括dup操作,取文件的执行时关闭标志,设置执行时关闭标志,取文件模式,设置文件的访问模式(堵塞模式也在这儿操作)
// 参数fd 是文件句柄,cmd 是操作命令(参见include/fcntl.h,23-30 行)。
int sys_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg)
{
struct file * filp;
// 如果文件句柄值大于一个进程最多打开文件数NR_OPEN,或者该句柄的文件结构指针为空,则出错,
// 返回出错码并退出。
if (fd >= NR_OPEN || !(filp = current->filp[fd]))
return -EBADF;
// 根据不同命令cmd 进行分别处理。
switch (cmd) {
case F_DUPFD: // 复制文件句柄。
return dupfd(fd,arg);
case F_GETFD: // 取文件句柄的执行时关闭标志。
return (current->close_on_exec>>fd)&1;
case F_SETFD: // 设置句柄执行时关闭标志。arg 位0 置位是设置,否则关闭。
if (arg&1)
current->close_on_exec |= (1<<fd);
else
current->close_on_exec &= ~(1<<fd);
return 0;
case F_GETFL: // 取文件状态标志和访问模式。
return filp->f_flags;
case F_SETFL: // 设置文件状态和访问模式(根据arg 设置添加、非阻塞标志)。
filp->f_flags &= ~(O_APPEND | O_NONBLOCK);
filp->f_flags |= arg & (O_APPEND | O_NONBLOCK);
return 0;
case F_GETLK: case F_SETLK: case F_SETLKW: // 未实现。
return -1;
default:
return -1;
}
}
4. ioctl.c
该版本的ioctl中只有对tty设备的控制,该函数是对不同设备的控制,后期的linux中该方法很强大了,可以对任何支持的驱动程序做特殊控制
网友评论