mac OSX 上的用户线程调度 API 使用 ucontext 和信号
User-threaded scheduling API on mac OSX using ucontext & signals
我正在设计一个具有以下特征的调度算法:
- 在一个进程中有 2 个用户线程(上下文)(我应该做 3 个线程,但在 osx 上还没有工作,所以我决定暂时让 2 个工作)
- 使用每 1 秒关闭一次的 SIGALRM 信号进行抢占,并将控制从一个上下文更改为另一个上下文,并在执行之前保存 运行ning 上下文的当前状态(寄存器和当前位置)开关。
我注意到的是:
- ucontext.h 库在 mac osx 上表现奇怪,而当它应用于 Linux 时,它的行为完全符合预期(这个人的例子link:http://man7.org/linux/man-pages/man3/makecontext.3.html 在 linux 上完美运行,而在 mac 上它在进行任何交换之前因分段错误而失败)。不幸的是,我必须在 osx 上做到 运行 而不是 linux。
- 我设法解决了 osx 上的 swapcontext 错误,方法是使用 getcontext() 然后使用 setcontext() 进行上下文交换。
- 在我的信号处理函数中,我使用 sa_sigaction( int sig, siginfo_t *s, void * cntxt ) 因为第三个变量曾经将它重铸为 ucontext_t 指针是关于被中断的上下文的信息(在 Linux 上我测试过它是真的)但是在 mac 上它没有指向正确的位置,因为当我使用它时我得到一个再次出现分段错误。
我已经为每个上下文设计了我的测试函数,以便在 while 循环内循环,因为我想中断它们并确保它们返回到该函数内的正确位置执行。我已经定义了一个静态全局计数变量,它可以帮助我查看我是否在正确的用户线程中。
最后要注意的是,我发现在测试函数中的 while 循环中调用 getcontext() 会不断更新我当前上下文的位置,因为它是空的 while 循环,因此调用 setcontext() 时上下文的时间到了使它从适当的地方执行。该解决方案是多余的,因为这些功能将从 API.
外部提供
#include <stdio.h>
#include <sys/ucontext.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
/*****************************************************************************/
/* time-utility */
/*****************************************************************************/
#include <sys/time.h> // struct timeval
void timeval_add_s( struct timeval *tv, uint64_t s ) {
tv->tv_sec += s;
}
void timeval_diff( struct timeval *c, struct timeval *a, struct timeval *b ) {
// use signed variables
long aa;
long bb;
long cc;
aa = a->tv_sec;
bb = b->tv_sec;
cc = aa - bb;
cc = cc < 0 ? -cc : cc;
c->tv_sec = cc;
aa = a->tv_usec;
bb = b->tv_usec;
cc = aa - bb;
cc = cc < 0 ? -cc : cc;
c->tv_usec = cc;
out:
return;
}
/******************************************************************************/
/* Variables */
/*****************************************************************************/
static int count;
/* For now only the T1 & T2 are used */
static ucontext_t T1, T2, T3, Main, Main_2;
ucontext_t *ready_queue[ 4 ] = { &T1, &T2, &T3, &Main_2 };
static int thread_count;
static int current_thread;
/* timer struct */
static struct itimerval a;
static struct timeval now, then;
/* SIGALRM struct */
static struct sigaction sa;
#define USER_THREAD_SWICTH_TIME 1
static int check;
/******************************************************************************/
/* signals */
/*****************************************************************************/
void handle_schedule( int sig, siginfo_t *s, void * cntxt ) {
ucontext_t * temp_current = (ucontext_t *) cntxt;
if( check == 0 ) {
check = 1;
printf("We were in main context user-thread\n");
} else {
ready_queue[ current_thread - 1 ] = temp_current;
printf("We were in User-Thread # %d\n", count );
}
if( current_thread == thread_count ) {
current_thread = 0;
}
printf("---------------------------X---------------------------\n");
setcontext( ready_queue[ current_thread++ ] );
out:
return;
}
/* initializes the signal handler for SIGALARM, sets all the values for the alarm */
static void start_init( void ) {
int r;
sa.sa_sigaction = handle_schedule;
sigemptyset( &sa.sa_mask );
sa.sa_flags = SA_SIGINFO;
r = sigaction( SIGALRM, &sa, NULL );
if( r == -1 ) {
printf("Error: cannot handle SIGALARM\n");
goto out;
}
gettimeofday( &now, NULL );
timeval_diff( &( a.it_value ), &now, &then );
timeval_add_s( &( a.it_interval ), USER_THREAD_SWICTH_TIME );
setitimer( ITIMER_REAL, &a, NULL );
out:
return;
}
/******************************************************************************/
/* Thread Init */
/*****************************************************************************/
static void thread_create( void * task_func(void), int arg_num, int task_arg ) {
ucontext_t* thread_temp = ready_queue[ thread_count ];
getcontext( thread_temp );
thread_temp->uc_link = NULL;
thread_temp->uc_stack.ss_size = SIGSTKSZ;
thread_temp->uc_stack.ss_sp = malloc( SIGSTKSZ );
thread_temp->uc_stack.ss_flags = 0;
if( arg_num == 0 ) {
makecontext( thread_temp, task_func, arg_num );
} else {
makecontext( thread_temp, task_func, arg_num, task_arg );
}
thread_count++;
out:
return;
}
/******************************************************************************/
/* Testing Functions */
/*****************************************************************************/
void thread_funct( int i ) {
printf( "---------------------------------This is User-Thread #%d--------------------------------\n", i );
while(1) { count = i;} //getcontext( ready_queue[ 0 ] );}
out:
return;
}
void thread_funct_2( int i ) {
printf( "---------------------------------This is User-Thread #%d--------------------------------\n", i );
while(1) { count = i;} //getcontext( ready_queue[ 1 ] ); }
out:
return;
}
/******************************************************************************/
/* Main Functions */
/*****************************************************************************/
int main( void ) {
int r;
gettimeofday( &then, NULL );
thread_create( (void *)thread_funct, 1, 1);
thread_create( (void *)thread_funct_2, 1, 2);
start_init();
while(1);
printf( "completed\n" );
out:
return 0;
}
- 我在这里做错了什么?我必须将其稍微更改为 运行 它在 Linux 上正确并且 运行 在 OSX 上 Linux 上运行的版本导致分段错误,但为什么它会在 OS 而不是这个?
- 这是否与我在每个上下文中分配的堆栈大小有关?
- 我应该为我的信号分配一个堆栈 space 吗? (它说如果我不这样做,它就会使用默认堆栈,如果我这样做也没有什么区别)?
- 如果使用 ucontext 永远不会在 mac osx 上产生可预测的行为,那么在 osx 上实现用户线程的替代方案是什么?我尝试使用 tmrjump & longjmp 但我 运行 遇到了同样的问题,即当上下文在执行某些函数的过程中被中断时,我如何才能获得上下文被中断的确切位置以便继续执行下次我离开了吗?
经过几天的测试和调试,我终于明白了。我不得不深入研究 ucontext.h 的实现并发现 2 OS 之间的差异。事实证明,ucontext.h 的 OSX 实现与 Linux 的不同。例如,ucontext_t 结构中的 mcontext_t 结构 n= 通常保存每个上下文的寄存器(PI、SP、BP、通用寄存器...)的值被声明为 [= 中的指针24=]X 而在 Linux 上则不是。需要特别设置 top 的其他一些差异是上下文的堆栈指针 (rsp) 寄存器、基指针 (rbp) 寄存器、指令指针 (rip) 寄存器、目标索引 (rdi) 寄存器……所有这些都必须在每个上下文的 beginining/creation 以及第一次 returns 之后正确设置。我还创建了一个 mcontext 结构来保存这些寄存器,并让我的 ucontext_t 结构的 uc_mcontext 指针指向它。完成所有这些后,我能够使用 sa_sigaction 信号处理函数中作为参数传递的 ucontext_t 指针(在我将其重铸为 ucontext_t 之后),以便准确恢复上下文上次停止的地方。归根结底,这是一件混乱的事情。有兴趣了解更多详情的可以私信我。 JJ出来了。
我正在设计一个具有以下特征的调度算法:
- 在一个进程中有 2 个用户线程(上下文)(我应该做 3 个线程,但在 osx 上还没有工作,所以我决定暂时让 2 个工作)
- 使用每 1 秒关闭一次的 SIGALRM 信号进行抢占,并将控制从一个上下文更改为另一个上下文,并在执行之前保存 运行ning 上下文的当前状态(寄存器和当前位置)开关。
我注意到的是:
- ucontext.h 库在 mac osx 上表现奇怪,而当它应用于 Linux 时,它的行为完全符合预期(这个人的例子link:http://man7.org/linux/man-pages/man3/makecontext.3.html 在 linux 上完美运行,而在 mac 上它在进行任何交换之前因分段错误而失败)。不幸的是,我必须在 osx 上做到 运行 而不是 linux。
- 我设法解决了 osx 上的 swapcontext 错误,方法是使用 getcontext() 然后使用 setcontext() 进行上下文交换。
- 在我的信号处理函数中,我使用 sa_sigaction( int sig, siginfo_t *s, void * cntxt ) 因为第三个变量曾经将它重铸为 ucontext_t 指针是关于被中断的上下文的信息(在 Linux 上我测试过它是真的)但是在 mac 上它没有指向正确的位置,因为当我使用它时我得到一个再次出现分段错误。
我已经为每个上下文设计了我的测试函数,以便在 while 循环内循环,因为我想中断它们并确保它们返回到该函数内的正确位置执行。我已经定义了一个静态全局计数变量,它可以帮助我查看我是否在正确的用户线程中。
最后要注意的是,我发现在测试函数中的 while 循环中调用 getcontext() 会不断更新我当前上下文的位置,因为它是空的 while 循环,因此调用 setcontext() 时上下文的时间到了使它从适当的地方执行。该解决方案是多余的,因为这些功能将从 API.
外部提供 #include <stdio.h>
#include <sys/ucontext.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
/*****************************************************************************/
/* time-utility */
/*****************************************************************************/
#include <sys/time.h> // struct timeval
void timeval_add_s( struct timeval *tv, uint64_t s ) {
tv->tv_sec += s;
}
void timeval_diff( struct timeval *c, struct timeval *a, struct timeval *b ) {
// use signed variables
long aa;
long bb;
long cc;
aa = a->tv_sec;
bb = b->tv_sec;
cc = aa - bb;
cc = cc < 0 ? -cc : cc;
c->tv_sec = cc;
aa = a->tv_usec;
bb = b->tv_usec;
cc = aa - bb;
cc = cc < 0 ? -cc : cc;
c->tv_usec = cc;
out:
return;
}
/******************************************************************************/
/* Variables */
/*****************************************************************************/
static int count;
/* For now only the T1 & T2 are used */
static ucontext_t T1, T2, T3, Main, Main_2;
ucontext_t *ready_queue[ 4 ] = { &T1, &T2, &T3, &Main_2 };
static int thread_count;
static int current_thread;
/* timer struct */
static struct itimerval a;
static struct timeval now, then;
/* SIGALRM struct */
static struct sigaction sa;
#define USER_THREAD_SWICTH_TIME 1
static int check;
/******************************************************************************/
/* signals */
/*****************************************************************************/
void handle_schedule( int sig, siginfo_t *s, void * cntxt ) {
ucontext_t * temp_current = (ucontext_t *) cntxt;
if( check == 0 ) {
check = 1;
printf("We were in main context user-thread\n");
} else {
ready_queue[ current_thread - 1 ] = temp_current;
printf("We were in User-Thread # %d\n", count );
}
if( current_thread == thread_count ) {
current_thread = 0;
}
printf("---------------------------X---------------------------\n");
setcontext( ready_queue[ current_thread++ ] );
out:
return;
}
/* initializes the signal handler for SIGALARM, sets all the values for the alarm */
static void start_init( void ) {
int r;
sa.sa_sigaction = handle_schedule;
sigemptyset( &sa.sa_mask );
sa.sa_flags = SA_SIGINFO;
r = sigaction( SIGALRM, &sa, NULL );
if( r == -1 ) {
printf("Error: cannot handle SIGALARM\n");
goto out;
}
gettimeofday( &now, NULL );
timeval_diff( &( a.it_value ), &now, &then );
timeval_add_s( &( a.it_interval ), USER_THREAD_SWICTH_TIME );
setitimer( ITIMER_REAL, &a, NULL );
out:
return;
}
/******************************************************************************/
/* Thread Init */
/*****************************************************************************/
static void thread_create( void * task_func(void), int arg_num, int task_arg ) {
ucontext_t* thread_temp = ready_queue[ thread_count ];
getcontext( thread_temp );
thread_temp->uc_link = NULL;
thread_temp->uc_stack.ss_size = SIGSTKSZ;
thread_temp->uc_stack.ss_sp = malloc( SIGSTKSZ );
thread_temp->uc_stack.ss_flags = 0;
if( arg_num == 0 ) {
makecontext( thread_temp, task_func, arg_num );
} else {
makecontext( thread_temp, task_func, arg_num, task_arg );
}
thread_count++;
out:
return;
}
/******************************************************************************/
/* Testing Functions */
/*****************************************************************************/
void thread_funct( int i ) {
printf( "---------------------------------This is User-Thread #%d--------------------------------\n", i );
while(1) { count = i;} //getcontext( ready_queue[ 0 ] );}
out:
return;
}
void thread_funct_2( int i ) {
printf( "---------------------------------This is User-Thread #%d--------------------------------\n", i );
while(1) { count = i;} //getcontext( ready_queue[ 1 ] ); }
out:
return;
}
/******************************************************************************/
/* Main Functions */
/*****************************************************************************/
int main( void ) {
int r;
gettimeofday( &then, NULL );
thread_create( (void *)thread_funct, 1, 1);
thread_create( (void *)thread_funct_2, 1, 2);
start_init();
while(1);
printf( "completed\n" );
out:
return 0;
}
- 我在这里做错了什么?我必须将其稍微更改为 运行 它在 Linux 上正确并且 运行 在 OSX 上 Linux 上运行的版本导致分段错误,但为什么它会在 OS 而不是这个?
- 这是否与我在每个上下文中分配的堆栈大小有关?
- 我应该为我的信号分配一个堆栈 space 吗? (它说如果我不这样做,它就会使用默认堆栈,如果我这样做也没有什么区别)?
- 如果使用 ucontext 永远不会在 mac osx 上产生可预测的行为,那么在 osx 上实现用户线程的替代方案是什么?我尝试使用 tmrjump & longjmp 但我 运行 遇到了同样的问题,即当上下文在执行某些函数的过程中被中断时,我如何才能获得上下文被中断的确切位置以便继续执行下次我离开了吗?
经过几天的测试和调试,我终于明白了。我不得不深入研究 ucontext.h 的实现并发现 2 OS 之间的差异。事实证明,ucontext.h 的 OSX 实现与 Linux 的不同。例如,ucontext_t 结构中的 mcontext_t 结构 n= 通常保存每个上下文的寄存器(PI、SP、BP、通用寄存器...)的值被声明为 [= 中的指针24=]X 而在 Linux 上则不是。需要特别设置 top 的其他一些差异是上下文的堆栈指针 (rsp) 寄存器、基指针 (rbp) 寄存器、指令指针 (rip) 寄存器、目标索引 (rdi) 寄存器……所有这些都必须在每个上下文的 beginining/creation 以及第一次 returns 之后正确设置。我还创建了一个 mcontext 结构来保存这些寄存器,并让我的 ucontext_t 结构的 uc_mcontext 指针指向它。完成所有这些后,我能够使用 sa_sigaction 信号处理函数中作为参数传递的 ucontext_t 指针(在我将其重铸为 ucontext_t 之后),以便准确恢复上下文上次停止的地方。归根结底,这是一件混乱的事情。有兴趣了解更多详情的可以私信我。 JJ出来了。