Chinaunix首页 | 论坛 | 博客
  • 博客访问: 407431
  • 博文数量: 61
  • 博客积分: 1991
  • 博客等级: 上尉
  • 技术积分: 492
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-08 12:28
文章分类

全部博文(61)

文章存档

2011年(5)

2010年(21)

2009年(3)

2008年(4)

2007年(28)

我的朋友

分类: BSD

2007-05-15 16:35:10

注:由于code是BBCode的关键字,在某些地方将程序中的变量code改写为_code

系统调用开始于用户程序,接着到达libc进行参数的包装,然后调用内核提供的机制进入内核。

内核提供的系统调用进入内核的方式有几种,包括lcall $X, y方式和
int 0x80方式。其实现都在sys/i386/i386/exception.s中。

我们看最常见的int 0x80入口。

1.int 0x80中断向量的初始化。
------------------

在i386CPU的初始化过程中,会调用函数init386() /*XXX*/
其中有:
代码: 

(sys/i386/i386/machdep.c)
-----------------------------------
setidt(IDT_SYSCALL, &IDTVEC(int0x80_syscall), SDT_SYS386TGT, SEL_UPL,
GSEL(GCODE_SEL, SEL_KPL));
-----------------------------------

在这里设置好int80的中断向量表。

代码:

(sys/i386/include/segments.h)
---------------------------------
#define IDT_SYSCALL 0x80 /* System Call Interrupt Vector */

#define SDT_SYS386TGT 15 /* system 386 trap gate */

#define SEL_UPL 3 /* user priority level */

#define GSEL(s,r) (((s)<<3) | r) /* a global selector */

#define GCODE_SEL 1 /* Kernel Code Descriptor */

#define SEL_KPL 0 /* kernel priority level */
----------------------------------

代码: 

(sys/i386/i386/machdep.c)
-----------------------------------
void
setidt(idx, func, typ, dpl, selec)
int idx;
inthand_t *func;
int typ;
int dpl;
int selec;
{
struct gate_descriptor *ip;

ip = idt + idx;
ip->gd_looffset = (int)func;
ip->gd_selector = selec;
ip->gd_stkcpy = 0;
ip->gd_xx = 0;
ip->gd_type = typ;
ip->gd_dpl = dpl;
ip->gd_p = 1;
ip->gd_hioffset = ((int)func)>>16 ;
}
------------------------------------

2.int0x80_syscall
------------------

系统调用的入口是int0x80_syscall,在sys/i386/i386/exception.s中。
它其实是一个包装函数,用汇编写成,其目的是为调用C函数syscall()做准备。
代码:

void
syscall(frame)
struct trapframe frame;

由于系统调用最终是要调用syscall()这个函数,
因此需要为它准备一个调用栈,包括参数frame,其类型为struct trapframe
代码: 

/*
* Exception/Trap Stack Frame
*/


struct trapframe {
int tf_fs;
int tf_es;
int tf_ds;
int tf_edi;
int tf_esi;
int tf_ebp;
int tf_isp;
int tf_ebx;
int tf_edx;
int tf_ecx;
int tf_eax;
int tf_trapno;
/* below portion defined in 386 hardware */
int tf_err;
int tf_eip;
int tf_cs;
int tf_eflags;
/* below only when crossing rings (e.g. user to kernel) */
int tf_esp;
int tf_ss;
};

这个trapframe实际上就是保存在核心栈上的用户态寄存器的状态,当从系统调用返回时,需要从这里恢复寄存器等上下文内容。同时,它又是函数syscall()的参数,这样在syscall()函数里面就可以方便地操纵返回后的用户进程上下文状态。

我们来看具体的int0x80_syscall。
代码: 

/*
* Call gate entry for FreeBSD ELF and Linux/NetBSD syscall (int 0x80)
*
* Even though the name says 'int0x80', this is actually a TGT (trap gate)
* rather then an IGT (interrupt gate). Thus interrupts are enabled on
* entry just as they are for a normal syscall.
*/

SUPERALIGN_TEXT
IDTVEC(int0x80_syscall)
pushl $2 /* sizeof "int 0x80" */

对照struct trapframe可知,此句赋值frame->tf_err=2,记录int 0x80指令的长度,因为有可能系统调用需要重新执行(系统调用返回ERESTART的话内核会自动重新执行),需要%eip的值减去int 0x80的指令长度。

代码:

subl $4,%esp /* skip over tf_trapno */
pushal
pushl %ds
pushl %es
pushl %fs

对照struct trapframe又可知,此时syscall(frame)的参数在堆栈上已经构造好。

代码: 

mov $KDSEL,%ax /* switch to kernel segments */
mov %ax,%ds
mov %ax,%es
mov $KPSEL,%ax
mov %ax,%fs

切换到内核数据段,并将%fs设置好,%fs指向一个per cpu的段,内存CPU相关的数据,比如当前线程的pcb和struct thread指针。

代码: 

FAKE_MCOUNT(13*4(%esp))
call syscall
MEXITCOUNT
jmp doreti

调用syscall()函数。syscall()返回后,将转到doreti(也在sys/i386/i386/exception.s中),判断是否可以执行AST,最后结束整个系统调用。

3.syscall()函数
---------------

我们接着看syscall()函数
代码:

/*
* syscall - system call request C handler
*
* A system call is essentially treated as a trap.
*/

void
syscall(frame)
struct trapframe frame;
{
caddr_t params;
struct sysent *callp;
struct thread *td = curthread;
struct proc *p = td->td_proc;
register_t orig_tf_eflags;
u_int sticks;
int error;
int narg;
int args[8];
u_int code;

/*
* note: PCPU_LAZY_INC() can only be used if we can afford
* occassional inaccuracy in the count.
*/

PCPU_LAZY_INC(cnt.v_syscall);

#ifdef DIAGNOSTIC
if (ISPL(frame.tf_cs) != SEL_UPL) {
mtx_lock(&Giant); /* try to stabilize the system XXX */
panic("syscall");
/* NOT REACHED */
mtx_unlock(&Giant);
}
#endif

sticks = td->td_sticks;
td->td_frame = &frame;
if (td->td_ucred != p->p_ucred)
cred_update_thread(td);

如果进程的user credential发生了改变,更新线程的相应指针。

代码: 

if (p->p_flag & P_SA)
thread_user_enter(p, td);

如果进程的线程模型采用scheduler activation,则需要通知用户态的线程manager
(FIXME)

代码: 

(sys/sys/proc.h)
#define P_SA 0x08000 /* Using scheduler activations. */

代码: 

params = (caddr_t)frame.tf_esp + sizeof(int);
code = frame.tf_eax;

params指向用户传递的系统调用参数。code指示是何种系统调用,后面还有描述。

代码:

orig_tf_eflags = frame.tf_eflags;

if (p->p_sysent->sv_prepsyscall) {
/*
* The prep code is MP aware.
*/

(*p->p_sysent->sv_prepsyscall)(&frame, args, &code, &params);

如果该进程有自己的系统调用准备函数,则调用之。事实上,所谓的系统调用准备函数,其作用应该就是对用户传进来的参数进行解释。如果没有准备函数,则内核做缺省处理,如下:

代码:

} else {
/*
* Need to check if this is a 32 bit or 64 bit syscall.
* fuword is MP aware.
*/

if (code == SYS_syscall) {
/*
* Code is first argument, followed by actual args.
*/

code = fuword(params);
params += sizeof(int);
} else if (code == SYS___syscall) {
/*
* Like syscall, but code is a quad, so as to maintain
* quad alignment for the rest of the arguments.
*/

code = fuword(params);
params += sizeof(quad_t);
}
}

如果该进程没有自己的系统调用准备函数,即缺省情况,则根据系统调用是32位还是64位,得到相应的具体系统号,并相应调整指向用户参数的指针。

SYS_syscall对应32位方式,
SYS___syscall对应64位方式。

函数fuword()意为fetch user word,即从用户空间拷贝一个word到内核空间来。其定义在
sys/i386/i386/support.s中,其实现与copyin()类似,我们略过。

此时,具体的系统调用号已经在变量code中了。

代码:

if (p->p_sysent->sv_mask)
code &= p->p_sysent->sv_mask;

对系统调用号做一些调整和限制。

代码: 

if ( code >= p->p_sysent->sv_size)
callp = &p->p_sysent->sv_table[0];
else
callp = &p->p_sysent->sv_table[_code];

得到系统调用的函数入口。

代码: 

narg = callp->sy_narg & SYF_ARGMASK;

得到该系统调用的参数个数。

代码: 

/*
* copyin and the ktrsyscall()/ktrsysret() code is MP-aware
*/

if (params != NULL && narg != 0)
error = copyin(params, (caddr_t)args,
(u_int)(narg * sizeof(int)));
else
error = 0;

将参数从用户态拷贝到内核态的args中。

代码: 

#ifdef KTRACE
if (KTRPOINT(td, KTR_SYSCALL))
ktrsyscall(code, narg, args);
#endif

/*
* Try to run the syscall without Giant if the syscall
* is MP safe.
*/

if ((callp->sy_narg & SYF_MPSAFE) == 0)
mtx_lock(&Giant);

如果该系统调用不是MP安全的,则获取全局锁。

代码: 

if (error == 0) {
td->td_retval[0] = 0;
td->td_retval[1] = frame.tf_edx;

STOPEVENT(p, S_SCE, narg);

PTRACESTOP_SC(p, td, S_PT_SCE);

error = (*callp->sy_call)(td, args);
}

调用具体的系统调用。
这里,之所以要间接地使用一个系统调用函数表,是因为模拟其他操作系统的需要。同一个系统调用在不同的操作系统里"系统调用号"是不同的,当运行其他操作系统的应用程序时,因为其编译结果是用其他操作系统的"系统调用号",此时需要转换到相应的FreeBSD的"系统调用号"上来,使用系统调用函数表就可以方便地作到这一点。

代码:

switch (error) {
case 0:
frame.tf_eax = td->td_retval[0];
frame.tf_edx = td->td_retval[1];
frame.tf_eflags &= ~PSL_C;
break;
 

Great,调用成功,设置返回值,并清除carry bit,用户态的libc要根据carry bit
判断系统调用是否成功。

代码:

case ERESTART:
/*
* Reconstruct pc, assuming lcall $X,y is 7 bytes,
* int 0x80 is 2 bytes. We saved this in tf_err.
*/

frame.tf_eip -= frame.tf_err;
break;

系统调用返回ERESTART,内核要尝试重新执行系统调用,因此需要将返回用户空间后的
%eip后退,具体后退几个字节,跟系统调用的进入方式有关,如果是通过int 0x80进入的,
由于int 0x80指令的长度为两个字节,因此回退2字节,如果是通过lcall $X,y方式进入
内核的,由于lcall $X,y指令的长度为7个字节,因此回退7字节。具体几个字节,在刚进入
时已经压到堆栈上了(前述pushl $2即是)。

代码: 

case EJUSTRETURN:
break;

default:
if (p->p_sysent->sv_errsize) {
if (error >= p->p_sysent->sv_errsize)
error = -1; /* XXX */
else
error = p->p_sysent->sv_errtbl[error];
}
frame.tf_eax = error;
frame.tf_eflags |= PSL_C;
break;
}

如果系统调用返回其他错误的话,则在进程的一个错误对应表中转换错误号。
并设置carry bit,以便libc知道。

代码:

/*
* Release Giant if we previously set it.
*/

if ((callp->sy_narg & SYF_MPSAFE) == 0)
mtx_unlock(&Giant);

释放全局锁。

代码:

/*
* Traced syscall.
*/

if ((orig_tf_eflags & PSL_T) && !(orig_tf_eflags & PSL_VM)) {
frame.tf_eflags &= ~PSL_T;
trapsignal(td, SIGTRAP, 0);
}

处理Traced系统调用。

代码: 

/*
* Handle reschedule and other end-of-syscall issues
*/

userret(td, &frame, sticks);

做一些调度处理等,后面另分析。

代码:

#ifdef KTRACE
if (KTRPOINT(td, KTR_SYSRET))
ktrsysret(code, error, td->td_retval[0]);
#endif

/*
* This works because errno is findable through the
* register set. If we ever support an emulation where this
* is not the case, this code will need to be revisited.
*/

STOPEVENT(p, S_SCX, code);

PTRACESTOP_SC(p, td, S_PT_SCX);

#ifdef DIAGNOSTIC
cred_free_thread(td);
#endif
WITNESS_WARN(WARN_PANIC, NULL, "System call %s returning",
(code >= 0 && code < SYS_MAXSYSCALL) ? syscallnames[_code] : "???");
mtx_assert(&sched_lock, MA_NOTOWNED);
mtx_assert(&Giant, MA_NOTOWNED);
}

4.userret()函数
-----------------

简要地看一下userret()函数。
代码:

/*
* Define the code needed before returning to user mode, for
* trap and syscall.
*
* MPSAFE
*/

void
userret(td, frame, oticks)
struct thread *td;
struct trapframe *frame;
u_int oticks;
{
struct proc *p = td->td_proc;

CTR3(KTR_SYSC, "userret: thread %p (pid %d, %s)", td, p->p_pid,
p->p_comm);
#ifdef INVARIANTS
/* Check that we called signotify() enough. */
PROC_LOCK(p);
mtx_lock_spin(&sched_lock);
if (SIGPENDING(td) && ((td->td_flags & TDF_NEEDSIGCHK) == 0 ||
(td->td_flags & TDF_ASTPENDING) == 0))
printf("failed to set signal flags properly for ast()\n");
mtx_unlock_spin(&sched_lock);
PROC_UNLOCK(p);
#endif

/*
* Let the scheduler adjust our priority etc.
*/

sched_userret(td);

调度器处理。

代码:

/*
* We need to check to see if we have to exit or wait due to a
* single threading requirement or some other STOP condition.
* Don't bother doing all the work if the stop bits are not set
* at this time.. If we miss it, we miss it.. no big deal.
*/

if (P_SHOULDSTOP(p)) {
PROC_LOCK(p);
thread_suspend_check(0); /* Can suspend or kill */
PROC_UNLOCK(p);
}

是否需要停住?系统的某些时候只允许单个线程运行。

代码:

/*
* Do special thread processing, e.g. upcall tweaking and such.
*/

if (p->p_flag & P_SA) {
thread_userret(td, frame);
}

又是scheduler activation的东西,通知用户态的thread manager。
(FIXME)

代码: 

/*
* Charge system time if profiling.
*/

if (p->p_flag & P_PROFIL) {
quad_t ticks;

mtx_lock_spin(&sched_lock);
ticks = td->td_sticks - oticks;
mtx_unlock_spin(&sched_lock);
addupc_task(td, TRAPF_PC(frame), (u_int)ticks * psratio);
}
}

最后是profiling的东西。

/c
#mkdir /mnt/d
#vi /etc/fstab
加入
/dev/ad0s1 /mnt/c msdos rw 0 0
/dev/ad0s2 /mnt/d msdos rw 0 0
ntfs和扩展分区的情况自己搞定吧,白云黄鹤上很多了
3)中文显示,输入一干子,
不用分那么清楚了,我自己记得都是怪事了
#vi ~/.bashrc【有没有这个文件都是这个明令】

alias vi='env LC_CTYPE=en_US.ISO_8859-1 vi'
alias ls='env LC_CTYPE=en_US.ISO_8859-1 ls -F'
【要用gnuls的不要这步了,复杂的东西不一定不可爱】
bind 'set convert-meta off'
bind 'set meta-flag on'
bind 'set output-meta on'
【没有这三行,我在zhcon里输入法打开,看不见自己输入的汉字
有点奇怪,但是加了我不保证能用】 //呵呵,tufeijoe兄真是点背,我就可以看到:) 4)哦,输入法还没有装,难怪不知道该如何下手了
我喜欢xsim,虽然不好,但是没有更好了,欧又不会五笔了
可惜这个没有ports
不过linux下的就可以拿过来用
这个我不细说了,一是我说过,相信精华区能够找到
二是xsim本身的文档太多,我完全按照这上面做的,改动很少
//我装的fcitx,感觉这个比其他的都好,现在linuxfans上紫光比较火,不过还没有移植到freebsd上,比较FT了
#whereis fcitx
/usr/ports/chinese/fcitx
cd /usr/ports/chinese/fcitx
make install clean
#vi ~/.xinitrc
应该有一行exec gnome-session //我的是gnome,不过一会要去试试kde了:)
在它前面加上
export XMODIFIERS="@im=fcitx"
export XIM=fcitx
export XIM_PROGRAM=fcitx
fcitx&
如果是csh,就改export为setenv,呵呵
进入X后如果fcitx起不来(不会起不来吧),就手动启动一下吧fcitx&,呵呵 【后备工作】
#vi ~/.profile
LC_ALL=zh_CN.EUC
export LC_ALL
LANG=zh_CN.EUC
export LANG
LC_CTYPE=zh_CN.EUC
export LC_CTYPE //这些都是使用bash要改的,使用csh的改~/.cshrc吧,好象叫这个名字,把export换成setenv

【不要说我占用你的眼睛,我觉得分开写,清晰】
5)退出会话,重新进入,准备兴奋吧
5、pp的东西总想据为己有,好东西最后拿出来看
1)我也很兴奋,差点就startx了
忘了装字体,不知道还忘了多少,不打算检查一番了
错误太多相信没有人看,让观众检查吧
找个simsun.ttf或者SIMSUN.TTF,自己想办法吧,相信你们能够找到
有句话叫做远在天边近在眼前
#cp simsun.ttf /usr/X11R6/lib/X11/fonts/
这个方法比较简单,就不需要额外的操作了
什么fonts.dir都滚到一边去,不过效果如何,你才是裁判 //好象说这样做效果并不是很好,但还可以忍受了,我就是这么做的:) 2)再来配置一下鼠标
这个玩艺\说难不难的,偏偏我刚学bsd的时候花了我大功夫
#vi /etc/X11/XF86Config
找到Section "InputDevice"关于鼠标的一节
在EndSection之前加
Option "Emulate3Buttons" //这个是模拟三键
Option "ZAxisMapping" "4 5" //这个是滚轮
这样,模拟三键,以及滑轮都可以支持了
【后来我才知道linux下去看看配置文件我两分种就可以搞定,
可惜阿,哀我之不幸】
3)还有什么咚咚么?没有了吧
是不是有点小心“一一”的输入startx
hoho,进去了,希望你能够启动
不过也要你装了kde才行吧,没有装还是/stand/sysinstall
界面很pp吧,这就是传说中的kde3。1,哈哈,gnome2
不能说不好,我喜欢我的桌面更大,我喜欢简洁,清晰,我。。。
不要扔我了,偶不说了
怎么样,fat分区的中文可以看了,整个界面中文化了(当然你要选择
简体中文才行),还没有完
傻瓜,又有点得意忘形了
4)哼哼,进入kde控制中心,如何进?问菜单去
字体---》全部改成simsun了,没有linux下装了字体后那种seris
不太懂,不过同样很pp,清晰
5)没有输入法阿,那里去找,
不要再找我,嘿嘿,没有启动
#vi ~/.xinitrc
加在exec startkde之前
export
xsim &
不要意思,重新启动kde保准生效,开始骂我蠢了
开个konsole,输入上面两行,当场生效,哈哈
6)还有什么呢,基本的都有了,
哦,qq,对不起,我装了gaim遇到很多问题,解决了再说 //我也还没有搞定,不过msn之类的可以,搞定了一定再写,呵呵 7)那给个office吧,
koffice不错阿,staroffice参考前面,不过bsd下我没有解决输入法
的问题,openoffice还没有来得及装,我有病阿,天天装软件干嘛
8)kdevelop
个人爱好吧
是不是搜索到两个阿?可惜偶不会日文
#cd /usr/ports/devel/kdevelop
#make install clean
装好后菜单里面自动添加,第一次运行有个图形界面配置过程
管它东西够不够,先是一路回车,搞定,可以用,还不错呢
9)差点又来废话了
qterm2还没有做bsd版的,我们就开始玩了,谢谢jjww,真是高手
//好象现在不用那么麻烦了,我直接就装上了,在ports里有,whereis找一下吧,不过要自己改字体 下tar.gz包
1. 修改configure
找到这样的语句
if test -z ""; then
if test "$kde_qtver" = "2"; then
if test $kde_qtsubver -gt 0; then
kde_qt_minversion=">= Qt 2.2.2"
else
kde_qt_minversion=">= Qt 2.0.2"
fi
fi
if test "$kde_qtver" = "3"; then
kde_qt_minversion=">= Qt 3.0.0-beta6"
fi
if test "$kde_qtver" = "1"; then
kde_qt_minversion=">= 1.42 and < 2.0"
fi
else
kde_qt_minversion=
fi
在这这段话之前加一句,强行设置。
kde_qtver=3
2. 修改qterm-0.2.0/qterm/qtermtelnet.h
#ifdef _OS_WIN32_
#include
#else
#include
#include //这一句是我加的,否则make会出现很多类型没定义。
#include
#include //这句删除,应该在前面
#include
#include //我加的,FreeBSD的sockaddr_in定义在这个头里。
#endif
我想原因是它们是基于linux,FreeBSD和linux的头文件可能不一样。
3. 安装
进入你的qterm-0.2.0目录。
Free48# ./configure --with-qt-libraries=/usr/X11R6/lib
10)vmware安装 //这东西我还没装过,我不喜欢
这个玩艺好像我完得倒是比较多,但是还是存在问题
只能用vmware2,不要想着装高版本了,涉及硬件方面的东西太多
没有办法轻松移植
#cd /usr/ports/emulators/vmware2
#make install clean
#mount_linprocfs linprocfs /compat/linux/proc
按照提示做吧,有点头晕
我还没有百分百的运用自如,就贴个序列号吧,这个文件放入
家目录的.vmware文件夹里面
#vi license

# VMware software license
Fields = "Cpt, Name, Email, LicenseType, LicenseClass,
LicenseFeature, Count, LicenseKey, ProductID, Product
Type, Expiration"
StartFields = "Cpt, Name, Email, LicenseType, Field2"
Field2 = "LicenseClass, LicenseFeature, Count, Field3"

Field3 = "LicenseKey, ProductID, ProductType, Expirati
on"
Cpt = "COPYRIGHT (c) 1998-2000 VMware, Inc."

Name = "iocc"
Email = ""
LicenseType = "User"
LicenseClass = "Full"
LicenseFeature = "None"
Count = "1 of 1"
LicenseKey = "148391_001"
ProductID = "VMware for Linux"
ProductType = "2.0"
Expiration = "2008-10-28"

Hash = 1b4b55d6-64536b60-65742569-e1c4d860-8edc82b9
6、其它
1)Stardict
【翻译软件】
#cd /usr/ports/chinese/stardict
#make install clean
在桌面建个连接,然后系统的在线翻译程序的图标还是很pp的
2)phoenix
我还没有去装,不过据说速度很快,
现在叫mozilla firebird吧,难怪mozilla的网站上没有搜索到 //呵呵,这个firebird还真大,源代码有30几M呢,不知道编译了多长时间,反正早上起来的时候编译完了:) 3)acrobat5
我安装系统的时候就装上了,不知道为什么有的人说不能显示中文
pdf,我没有遇到这个情况
4)realone
下个bin,直接运行,不能再简单了,但是相信在linux下用着爽点
5)mkisofs
#cd /usr/ports/chinese/mkisofs
要做iso你找它吧,简单得不能再简单
6)ftp
有个图形的IglooFTP,再来个经典的wget就不错了
说了这么久,相信在哪个位置应该知道了吧
还不会?
再介绍一种方法
#cd /usr/ports
#make search key=wget
7)其它的我实在想不起来好要装什么
因为需要而学习,因为需要而装
有么问题,大家说出来讨论讨论嘛,你不说我怎么知道你有问题。。。。
唐僧了
挂了,今天不停电,该给我放下假了
7、最后
最后还是希望大家多交流,多做记录,脑袋并不是那么可靠
总有忘记的时候
8、补充点
显卡驱动的安装
//驱动我装上了,去nvidia的官方网站下的,不过今天又出了新版本,我昨天下的,真是FT,很简单的,先解压缩,然后make setup,就装好了,修改/etc/X11/XF86Config文件,把nv改成nvidia,注释掉装系统时识别出来的MX400之类的,没有?没有就算了,呵呵,startx吧,是不是nvidia的标志出来了? win下vmware的安装、使用
//这个不用说什么吧,win下的安装,使用都不难的
quake3的安装 //我不喜欢打哦:)
等等
本来想一并整理进来,不过又怕废话一大堆,不说了,
欢迎访问telnet bbs.whnet.edu.cn
电脑技术|FreeBSD版
支持web访问!
Tab键看进版画面,z键进秘密备忘录,x键进精华区
精华区。。。。缺少一些新文章阿
我这篇算新“问”题章
可惜不是好文章。。。。。。。。。。。。。。。。。。。。。。

阅读(2694) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~