linux0.12之内核代码signal.c说明

原创 2015年07月08日 15:35:07

首先理论理解一下
信号是一种“软件中断”处理机制。信号机制提供了处理异步事件的方法。
信号也叫软中断。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。
内核代码中使用无符号长整型(32bit)编码信号,被定义在include/signal.h中

typedef unsigned int sigset_t;   /* 32 bits */
#define _NSIG             32
#define NSIG     _NSIG
#define SIGHUP   1
#define SIGINT   2
#define SIGQUIT  3
#define SIGILL   4
#define SIGTRAP  5
#define SIGABRT  6
#define SIGIOT   6
#define SIGUNUSED    7
#define SIGFPE   8
#define SIGKILL  9
#define SIGUSR1  10
#define SIGSEGV  11
#define SIGUSR2  12
#define SIGPIPE  13
#define SIGALRM  14
#define SIGTERM  15
#define SIGSTKFLT   16
#define SIGCHLD  17
#define SIGCONT  18
#define SIGSTOP  19
#define SIGTSTP  20
#define SIGTTIN  21
#define SIGTTOU  22

do_signal函数是由内核系统 调用(int 0x80)中断处理程序中对信号的预处理程序
如下有个信号处理程序的调用方式
这里写图片描述
我们知道从用户态到内核态,cpu会压入上下文,如下
这里写图片描述
如果正常返回,是继续执行cs:Eip地址的代码,而do_signal的作用就是修改代码执行
这里写图片描述
do_signal根据消息类型,将函数指针指向相应的信息处理函数
这里写图片描述
当系统调用或者中断返回的时候会调用do_signal函数。这个函数的主要作用是将信号处理句柄插入到用户程序堆栈中,并在本系统调用结束返回手立刻进入执行信号句柄程序,然后继续执行用户的程序。

看几个典型的函数
1、do_signal函数

int do_signal(long signr,long eax,long ebx, long ecx, long edx, long orig_eax,
 long fs, long es, long ds,
 long eip, long cs, long eflags,
 unsigned long * esp, long ss)

传递了很多寄存器参数
此函数是在system_call.s(系统调用)中调用的
call _do_signal
第一条代码
struct sigaction * sa = current->sigaction + signr - 1;
首先sigaction是current结构体中一个元素

struct task_struct {
/* these are hardcoded - don't touch */
 long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
 long counter;
 long priority;
 long signal;
 struct sigaction sigaction[32];
.......................
}

current->sigaction是数组的第一个地址,所以
左值等效于
current->sigaction [signr - 1]
而这个数组又是一个结构体类型的

struct sigaction {
 void (*sa_handler)(int);
 sigset_t sa_mask;
 int sa_flags;
 void (*sa_restorer)(void);
};

下面看if语句
if ((orig_eax != -1) && ((eax == -ERESTARTSYS) || (eax == -ERESTARTNOINTR)))
看看orig_eax,因为本do_signal函数是从system_call.s中调用的,
回到系统调用函数,看看是怎么压栈的
long orig_eax,long fs, long es, long ds,这是参数传递的顺序,压栈顺序相反
第一个:从system_call入口

_system_call:
 push %ds
 push %es
 push %fs
 pushl %eax  # save the orig_eax

第二个:从时间中断入口

_timer_interrupt:
 push %ds    # save ds,es and put kernel data space
 push %es    # into them. %fs is used by _system_call
 push %fs
 pushl $-1  # fill in -1 for orig_eax

第三个:从检测设备入口

_device_not_available:
 push %ds
 push %es
 push %fs
 pushl $-1  # fill in -1 for orig_eax

可以看出如果不是系统调用而是其他中断执行过程中断调用到本函数,riog_eax为-1.

sa_handler = (unsigned long) sa->sa_handler;
 if (sa_handler==1)
  return(1);   /* Ignore, see if there are more signals... */
 if (!sa_handler) 

其中sahandler是信号处理句柄
在signal.h中

#define SIG_DFL  ((void (*)(int))0) /* default signal handling */
#define SIG_IGN  ((void (*)(int))1) /* ignore signal */
#define SIG_ERR  ((void (*)(int))-1)    /* error return from signal */

这种宏第一定义用0、1表示函数指针类型,当为0时默认处理信号

if (!sa_handler) {
  switch (signr) {
  case SIGCONT:
  case SIGCHLD:
   return(1);  /* Ignore, ... */
  case SIGSTOP:
  case SIGTSTP:
  case SIGTTIN:
  case SIGTTOU:
   current->state = TASK_STOPPED;
   current->exit_code = signr;
   if (!(current->p_pptr->sigaction[SIGCHLD-1].sa_flags &
     SA_NOCLDSTOP))
    current->p_pptr->signal |= (1<<(SIGCHLD-1));
   return(1);  /* Reschedule another event */
  case SIGQUIT:
  case SIGILL:
  case SIGTRAP:
  case SIGIOT:
  case SIGFPE:
  case SIGSEGV:

然后根据信号类型完成不同处理,包括修改进程的运行状态,再具体,还不是很了解。
下面看看invoking a handler
*(&eip) = sa_handler;
longs = (sa->sa_flags & SA_NOMASK)?7:8;
*(&esp) -= longs;
将sa_handler地址放入eip,并将esp向下扩展7(8)个长字,
verify_area(esp,longs*4);
其中verify_area函数,是内存验证函数

oid verify_area(void * addr,int size)
{
 unsigned long start;
 start = (unsigned long) addr;
 size += start & 0xfff;
 start &= 0xfffff000;
 start += get_base(current->ldt[2]);
 while (size>0) {
  size -= 4096;
  write_verify(start);
  start += 4096;
 }
}

这个函数是写操作前检查,(这里会涉及到copy_on_write的概念),
start = (unsigned long) addr;
获得内存地址
start & 0xfff; 4K偏移量(页面中的偏移量),
size += start & 0xfff;
这样就得到以页面开始的大小
示意图如下
这里写图片描述
start &= 0xfffff000;获得页对齐的地址
start += get_base(current->ldt[2]);
谷歌讲这是把start加上进程数据段在线性地址空间中的起始位置,变成系统整个线性空间中的地址位置。我不是太理解。
while (size>0) {
size -= 4096;
write_verify(start);
start += 4096;
}
函数write_verify是写页面验证,
void write_verify(unsigned long address)
{
unsigned long page;
if (!( (page = ((unsigned long ) ((address>>20) & 0xffc)) )&1))
return;
page &= 0xfffff000;
page += ((address>>10) & 0xffc);
if ((3 & (unsigned long ) page) == 1) /* non-writeable, present */
un_wp_page((unsigned long *) page);
return;
}
参数addres是线性地址,
if (!( (page = ((unsigned long ) ((address>>20) & 0xffc)) )&1))
return;
page得到的是页目录表项的地址中的数据
然后检查P位,是否置一,为0说明没有使用,对于没有使用的页面而言,没有共享和写时复用的概念。
page &= 0xfffff000;
获得页表地址基地址;
((address>>10) & 0xffc)从线性地址中获得页表项偏移地址
page += ((address>>10) & 0xffc);
获得页表项地址
if ((3 & (unsigned long ) page) == 1)
检测页表项内容,11b模式,即检测位1(R/W=1)、位0(P),如果不可写,并且存在,就要进行写时复制,
void un_wp_page(unsigned long * table_entry)
{
unsigned long old_page,new_page;
old_page = 0xfffff000 & *table_entry;
if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
*table_entry |= 2;
invalidate();
return;
}
if (!(new_page=get_free_page()))
oom();
if (old_page >= LOW_MEM)
mem_map[MAP_NR(old_page)]–;
copy_page(old_page,new_page);
*table_entry = new_page | 7;
invalidate();
}
输入参数是页表项指针,为物理地址。
old_page = 0xfffff000 & *table_entry;
获取页帧地址,即内存页地址
if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
*table_entry |= 2;
invalidate();
return;
}
内存页空间不再内核空间,并且只有一个进程共享,则修改页表项参数,变为可写

#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0))

cr3是页目录表基地址,就是刷新页目录高速缓冲
if (!(new_page=get_free_page()))
oom();
if (old_page >= LOW_MEM)
mem_map[MAP_NR(old_page)]–;
copy_page(old_page,new_page);
*table_entry = new_page | 7;
invalidate();
否则申请一页内存;并将数据复制到新的页内存中。

这里面还有好多,没能理解。。。。只是梳理了一点内容

版权声明:本文为博主原创文章,未经博主允许不得转载。

linux0.12之内核代码之fork.c说明

在system_call.s中.align 2 _sys_fork: call _find_empty_process testl %eax,%eax js 1f push %gs push...

《linux 内核完全剖析》 signal.c 代码分析笔记

signal.c 代码分析笔记 int sys_sgetmask()// 获取当前进程阻塞的信号 { returncurrent->blocked; }   int s...

linux 0.12之head.s跳转到main.c的说明

因为只有引导代码中使用了as86的编译器,linux0.12其他汇编都是基于GNU as汇编的,这是背景。 head.s是在gnu as下编译的,使用的是AT&T汇编模式。主要完成一些GDT的初始化...

LINUX内核完全剖析:基于0.12内核

  • 2016年01月14日 20:50
  • 198.97MB
  • 下载

bochs+gdb源码级调试Linux 0.12内核测试环境构建

http://www.linuxidc.com/Linux/2011-06/36746.htm之前都是用的bochs自带的debug功能,但是linux内核大部分都是用C语言来实现的,若仅通过反汇编的...
  • ztguang
  • ztguang
  • 2016年02月01日 00:00
  • 224

Linux内核0.12——8086中断

中断:CPU不再继续依序执行指令,而是转去处理某一从CPU外部或内部产生的特殊信息 从汇编角度理解: 内中断:对于8086CPU来说,以下发生在CPU内部的的情况会产生内中断: 除法错误;单步执...

linux内核完全剖析0.12笔记--第四章 80x86保护模式及其编程

这一章涉及intel8086系列cpu的保护模式编程,应该是学习内核编程,驱动编程及嵌入式编程一些基础知识。不过对于没接触过底层编程的我来说,感觉还是好复杂。   不过里面也有许多以前汇编学过的东西...

linux内核0.12之MINIX文件系统初探

春节期间确实懒了,博客没有怎么更新。这节日快过了,心里却很不踏实,最近没学习啊。今天果断看点文件系统的知识。minix文件系统现在是很少用了,但是基本的东西还是不变的。本着占了茅坑就一定要拉的责任感,...

Linux 0.12 内核对内存的管理

Linux 0.12 内核对内存的管理 其着重点在于分段,用分段的机制把进程间的虚拟地址分隔开。 每个进程都有一张段表LDT,整个系统有一张GDT表,且整个系统只有一个总页表。 ...

Linux内核0.12——微机组成结构

冯诺依曼体系结构计算机:CPU、ALU、存储器、输入输出设备 总线: 传统PC总线:CPU通过数据总线、地址总线和控制总线与外部设备进行通信,所有的外部设备都将连都这三条总线上,也就是说只有CPU...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:linux0.12之内核代码signal.c说明
举报原因:
原因补充:

(最多只允许输入30个字)