系统调用代码流程

由用户态进入内核态时,CPU会自动按照SS、ESP、EFLAGS、CS、EIP的顺序,将这几个寄存器的值压入到内核栈中
父进程内核栈的样子
执行int 0x80将SS、ESP、EFLAGS、CS、EIP入栈。
在system_call中将DS、ES、FS、EDX、ECX、EBX入栈。

system_call:
    cmpl $nr_system_calls-1,%eax  调用号如果超出范围的话就在 eax 中置-1 并退出
    ja bad_sys_call 
    push %ds    # 保存原段寄存器值
    push %es
    push %fs
    pushl %edx     # ebx,ecx,edx 中放着系统调用相应的 C 语言函数的调用参数
    pushl %ecx      # push %ebx,%ecx,%edx as parameters
    pushl %ebx      # to the system call
    movl $0x10,%edx        # set up ds,es to kernel space
    mov %dx,%ds
    mov %dx,%es          ds,es 指向内核数据段(全局描述符表中数据段描述符)。
    movl $0x17,%edx        # fs points to local data space
    mov %dx,%fs                  fs 指向局部数据段(局部描述符表中数据段描述符)。
    # 下面这句操作数的含义是:调用地址 = _sys_call_table + %eax * 4。参见列表后的说明。
# 对应的 C 程序中的 sys_call_table 在 include/linux/sys.h 中,其中定义了一个包括 72 个
# 系统调用 C 处理函数的地址数组表。
    call sys_call_table(,%eax,4)  
###
在一个 8 字节为一个记录的数组中寻址指定的字符。其中 eax 中是指定的记录号,ebx 中是指定字
符在记录中的偏移址:
AT&T: _array(%ebx,%eax,8) Intel: [ebx + eax*8 + _array]
####

    pushl %eax       # 把系统调用号入栈。
    movl current,%eax    # 取当前任务(进程)数据结构地址Îeax。
#下面 4 行查看当前任务的运行状态。 如果不在就绪状态(state 不等于 0)就去执行调度程序。
# 如果该任务在就绪状态但 counter[??]值等于 0,则也去执行调度程序。
    cmpl $0,state(%eax)        # state
    jne reschedule
    cmpl $0,counter(%eax)      # counter
    je reschedule

在system_call中执行完相应的系统调用sys_call_xx后,又将函数的返回值eax压栈。若引起调度,则跳转执行reschedule。否则则执行ret_from_sys_call。

reschedule:
	pushl $ret_from_sys_call
	jmp schedule

在执行schedule前将ret_from_sys_call压栈,因为schedule是c函数,所以在c函数末尾的},相当于ret指令,将会弹出ret_from_sys_call作为返回地址,跳转 ret_from_sys_call执行。
总之,在系统调用结束后,将要中断返回前,内核栈的样子如下:
在这里插入图片描述

void schedule(void)
{
    int i,next,c;
    struct task_struct *pnext = &(init_task.task);
    struct task_struct ** p;    // 任务结构指针的指针。
    /* check alarm, wake up any interruptible tasks that have got a signal */
/* 检测 alarm,唤醒得到了一个信号的任何可以中断的任务 */
// 从任务数组中最后一个任务开始检测 alarm。
	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
		if (*p) {
		// 如果任务的 alarm 时间已经过期(alarm<jiffies),在信号位图中置 SIGALRM 信号,然后清 alarm。
			if ((*p)->alarm && (*p)->alarm < jiffies) {
					(*p)->signal |= (1<<(SIGALRM-1));
					(*p)->alarm = 0;
				}
// 如果信号位图中除去被阻塞的信号外还有其它信号并且任务处于可中断状态,则置任务为就绪态。
// 其中'~(_BLOCKABLE & (*p)->blocked)'用于忽略被阻塞的信号,但 SIGKILL 和 SIGSTOP 不能被阻塞。
			if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
			(*p)->state==TASK_INTERRUPTIBLE)
				(*p)->state=TASK_RUNNING;  //置为就绪(可执行)状态。
		}
/* 这里是调度程序的主要部分 */
	while (1) {
		c = -1;
		next = 0;
		i = NR_TASKS;
		p = &task[NR_TASKS];
	// 这段代码比较每个就绪状态任务的 counter 值,哪一个大 next 就指向哪个的任务号。
		while (--i) {
			if (!*--p)
				continue;
			if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
				c = (*p)->counter, next = i;
		}
	// 如果比较得出有 counter 值大于 0 的结果,则退出循环,执行任务调度切换(switch_to(next);)。
		if (c) break;
	// 否则就根据每个任务的优先权值,更新每一个任务的 counter 值,然后回到 125 行重新比较。
// counter 值的计算方式为 counter = counter /2 + priority。
		for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
			if (*p)
				(*p)->counter = ((*p)->counter >> 1) +
						(*p)->priority;
	}
	switch_to(next);// 切换到任务号为 next 的任务,并运行之。
    
}

在schedule()函数中,当调用函数switch_to(pent, _LDT(next))时,会依次将返回地址}、参数2 _LDT(next)、参数1 pnext压栈。当执行switch_to的返回指令ret时,就回弹出schedule()函数的}执行schedule()函数的返回指令}。关于执行switch_to时内核栈的样子,在后面改写switch_to函数时十分重要。
此处将跳入到switch_to中执行时,内核栈的样子如下:
在这里插入图片描述

/*
 *	switch_to(n) should switch tasks to task nr n, first
 * checking that n isn't the current task, in which case it does nothing.
 * This also clears the TS-flag if the task we switched to has used
 * tha math co-processor latest.
 */
 * switch_to(n)将切换当前任务到任务 nr,即 n。首先检测任务 n 不是当前任务,
* 如果是则什么也不做退出。如果我们切换到的任务最近(上次运行)使用过数学
* 协处理器的话,则还需复位控制寄存器 cr0 中的 TS 标志。
// 输入:%0 - 新 TSS 的偏移地址(*&__tmp.a); %1 - 存放新 TSS 的选择符值(*&__tmp.b);
// dx - 新任务 n 的选择符;ecx - 新任务指针 task[n]。
// 其中临时数据结构__tmp 中,a 的值是 32 位偏移值,b 为新 TSS 的选择符。在任务切换时,a 值
// 没有用(忽略)。
#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,current\n\t" \ // 任务 n 是当前任务吗?(current ==task[n]?)
	"je 1f\n\t" \    // 是,则什么都不做,退出。
	"movw %%dx,%1\n\t" \   // 将新任务的选择符->*&__tmp.b。
	"xchgl %%ecx,current\n\t" \  // current = task[n];ecx = 被切换出的任务。
	"ljmp *%0\n\t" \     // 长跳转,造成任务切换。
	"cmpl %%ecx,last_task_used_math\n\t" \   // 新任务上次使用过协处理器吗?
	"jne 1f\n\t" \    // 没有则跳转,退出。
	"clts\n" \   // 新任务上次使用过协处理器,则清 cr0 的 TS 标志。
	"1:" \
	::"m" (*&__tmp.a),"m" (*&__tmp.b), \
	"d" (_TSS(n)),"c" ((long) task[n])); \
}

要实现基于内核栈的任务切换,主要完成如下三件工作

(1)重写 switch_to;
(2)将重写的 switch_to 和 schedule() 函数接在一起;
(3)修改现在的 fork()。

schedule 与 switch_to
目前 Linux 0.11 中工作的 schedule() 函数是首先找到下一个进程的数组位置 next,而这个 next 就是 GDT 中的 n,所以这个 next 是用来找到切换后目标 TSS 段的段描述符的,一旦获得了这个 next 值,直接调用上面剖析的那个宏展开 switch_to(next);就能完成如图 TSS 切换所示的切换了。

现在,我们不用 TSS 进行切换,而是采用切换内核栈的方式来完成进程切换,所以在新的 switch_to 中将用到当前进程的 PCB、目标进程的 PCB、当前进程的内核栈、目标进程的内核栈等信息。由于 Linux 0.11 进程的内核栈和该进程的 PCB 在同一页内存上(一块 4KB 大小的内存),其中 PCB 位于这页内存的低地址,栈位于这页内存的高地址;另外,由于当前进程的 PCB 是用一个全局变量 current 指向的,所以只要告诉新 switch_to()函数一个指向目标进程 PCB 的指针就可以了。同时还要将 next 也传递进去,虽然 TSS(next)不再需要了,但是 LDT(next)仍然是需要的,也就是说,现在每个进程不用有自己的 TSS 了,因为已经不采用 TSS 进程切换了,但是每个进程需要有自己的 LDT,地址分离地址还是必须要有的,而进程切换必然要涉及到 LDT 的切换。

综上所述,需要将目前的 schedule() 函数(在 kernal/sched.c 中)做稍许修改,即将下面的代码:

if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
    c = (*p)->counter, next = i;

//......

switch_to(next);
copy

修改为:

if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
    c = (*p)->counter, next = i, pnext = *p;

//.......

switch_to(pnext, LDT(next));

实现 switch_to
删除头文件sched.h中的长跳转指令:“ljmp *%0\n\t”
在system_call.s中添加系统调用函数switch_to():

.align 2
switch_to:
	pushl %ebp
	movl %esp,%ebp
	pushl %ecx
	pushl %ebc
	pushl %eax

	movl 8(%ebp),%ebx
	cmpl %ebx,current
	je 1f

 // PCB的切换
	movl %ebx,%eax
	xchgl %eax,current
	
	// TSS中内核栈指针的重写
	movl tss,%ecx
	addl $4096,%ebx
	movl %ebx,ESP0(%ecx)

	//切换内核栈
	movl %esp,KERNEL_STACK(%eax)
	movl 8(%ebp),%ebx
	movl KERNEL_STACK(%ebx),%esp

	//LDT的切换
	movl 12(%ebp),%ecx
	lldt %cx
	movl $0x17,%ecx
	mov %cx,%fs
	
	movl $0x17,%ecx
	mov %cx,%fs
	cmpl %eax,last_task_used_math
	jne 1f
	clts

1:	popl %eax
	popl %ebx
	popl %ecx
	popl %ebp
	ret

fs 是一个选择子,即 fs 是一个指向描述符表项的指针,这个描述符才是指向实际的用户态内存的指针,所以上一个进程和下一个进程的 fs 实际上都是 0x17,真正找到不同的用户态内存是因为两个进程查的 LDT 表不一样,所以这样重置一下 fs=0x17 有用吗,有什么用?要回答这个问题就需要对段寄存器有更深刻的认识,实际上段寄存器包含两个部分:显式部分和隐式部分,如下图给出实例所示,就是那个著名的 jmpi 0, 8,虽然我们的指令是让 cs=8,但在执行这条指令时,会在段表(GDT)中找到 8 对应的那个描述符表项,取出基地址和段限长,除了完成和 eip 的累加算出 PC 以外,还会将取出的基地址和段限长放在 cs 的隐藏部分,即图中的基地址 0 和段限长 7FF。为什么要这样做?下次执行 jmp 100 时,由于 cs 没有改过,仍然是 8,所以可以不再去查 GDT 表,而是直接用其隐藏部分中的基地址 0 和 100 累加直接得到 PC,增加了执行指令的效率。
更改fork.c

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
		long ebx,long ecx,long edx,
		long fs,long es,long ds,
		long eip,long cs,long eflags,long esp,long ss)
{
	struct task_struct *p;
	int i;
	struct file *f;


	p->tss.back_link = 0;
	p->tss.esp0 = PAGE_SIZE + (long) p;
	p->tss.ss0 = 0x10;


	*(--krnstack) = ss & 0xffff;
	*(--krnstack) = esp;
	*(--krnstack) = eflags;
	*(--krnstack) = cs & 0xffff;
	*(--krnstack) = eip;

	*(--krnstack) = (long) first_return_kernel;//处理switch_to返回的位置

	*(--krnstack) = ebp;
	*(--krnstack) = ecx;
	*(--krnstack) = ebx;
	*(--krnstack) = 0;

	//把switch_to中要的东西存进去
	p->kernelstack = krnstack;
	...

写first_return_kernel在system_call.s中:

首先需要将first_return_kernel设置在全局可见:
.globl switch_to,first_return_kernel

然后需要在fork.c中添加该函数的声明:
extern void first_return_from_kernel(void);

最后就是将具体的函数实现放在system_call.s头文件里面:
first_return_kernel:
 popl %edx
 popl %edi
 popl %esi
 pop %gs
 pop %fs
 pop %es
 pop %ds
 iret

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深交所交易接口调用流程一般如下: 1. 连接交易服务器:使用交易接口提供的函数连接深交所交易服务器,获取连接句柄。 2. 登录交易账户:使用交易接口提供的函数登录交易账户,进行身份认证。 3. 查询交易数据:使用交易接口提供的函数查询交易数据,如可买卖股票数量、当日成交记录等。 4. 下单交易:使用交易接口提供的函数下单交易,包括买卖操作、股票代码、价格、数量等参数。 5. 查询交易结果:使用交易接口提供的函数查询交易结果,如成交记录、委托状态等。 以下是一个简单的深交所交易接口用例代码,仅供参考: ``` // 连接交易服务器 HMODULE hModule = LoadLibrary(_T("trade.dll")); FunCreateTrade CreateTrade = (FunCreateTrade)GetProcAddress(hModule, "CreateTrade"); FunDestroyTrade DestroyTrade = (FunDestroyTrade)GetProcAddress(hModule, "DestroyTrade"); FunConnectServer ConnectServer = (FunConnectServer)GetProcAddress(hModule, "ConnectServer"); FunDisConnectServer DisConnectServer = (FunDisConnectServer)GetProcAddress(hModule, "DisConnectServer"); FunGetConnectStatus GetConnectStatus = (FunGetConnectStatus)GetProcAddress(hModule, "GetConnectStatus"); FunGetErrorMsg GetErrorMsg = (FunGetErrorMsg)GetProcAddress(hModule, "GetErrorMsg"); TradeApi *pTrade = CreateTrade(); pTrade->SetServerAddr(_T("123.45.67.89"), 12345); pTrade->SetVersion(_T("1.0")); pTrade->SetAccountInfo(_T("1234567890"), _T("password"), _T("123456"), _T("01")); pTrade->SetClientInfo(_T("myapp"), _T("1.0")); if (ConnectServer(pTrade) != 0) { CString strErrorMsg = GetErrorMsg(pTrade); // 连接失败,处理错误信息 } else { // 连接成功,执行后续操作 } // 登录交易账户 FunUserLogin UserLogin = (FunUserLogin)GetProcAddress(hModule, "UserLogin"); FunUserLogout UserLogout = (FunUserLogout)GetProcAddress(hModule, "UserLogout"); FunGetUserInfo GetUserInfo = (FunGetUserInfo)GetProcAddress(hModule, "GetUserInfo"); if (UserLogin(pTrade) != 0) { CString strErrorMsg = GetErrorMsg(pTrade); // 登录失败,处理错误信息 } else { // 登录成功,获取账户信息 UserInfo userInfo; GetUserInfo(pTrade, &userInfo); } // 查询交易数据 FunQueryData QueryData = (FunQueryData)GetProcAddress(hModule, "QueryData"); FunGetStockInfo GetStockInfo = (FunGetStockInfo)GetProcAddress(hModule, "GetStockInfo"); QueryData(pTrade, QUERY_STOCK); int nStockCount = GetStockCount(pTrade); for (int i = 0; i < nStockCount; i++) { StockInfo stockInfo; GetStockInfo(pTrade, i, &stockInfo); // 处理股票信息 } // 下单交易 FunPlaceOrder PlaceOrder = (FunPlaceOrder)GetProcAddress(hModule, "PlaceOrder"); OrderInfo orderInfo; _tcscpy_s(orderInfo.szStockCode, _T("600001")); orderInfo.nTradeType = 1; // 买入 orderInfo.nPriceType = 1; // 限价单 orderInfo.fPrice = 10.0; orderInfo.nQuantity = 100; if (PlaceOrder(pTrade, &orderInfo) != 0) { CString strErrorMsg = GetErrorMsg(pTrade); // 下单失败,处理错误信息 } else { // 下单成功,等待交易结果 } // 查询交易结果 FunQueryOrder QueryOrder = (FunQueryOrder)GetProcAddress(hModule, "QueryOrder"); QueryOrder(pTrade, QUERY_ALL); int nOrderCount = GetOrderCount(pTrade); for (int i = 0; i < nOrderCount; i++) { OrderInfo orderInfo; GetOrderInfo(pTrade, i, &orderInfo); // 处理订单信息 } // 断开连接 DisConnectServer(pTrade); DestroyTrade(pTrade); FreeLibrary(hModule); ``` 以上代码仅为示例,实际使用时需要根据具体情况进行调整和优化。同时,需要注意深交所交易接口在不同版本和操作系统上可能存在差异,需要仔细查阅相关文档和API参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值