我正在调查rep字符串指令的工作原理。关于指令的描述,rep movsl例如具有以下助记符。
rep movsl 5+4*(E)CX Move (E)CX dwords from [(E)SI] to ES:[(E)DI]
其中ES是额外的段寄存器,应该包含一些偏移到存储器中额外段的开头。
操作的伪代码如下所示
while (CX != 0) {
*(ES*16 + DI) = *(DS*16 + SI);
SI++;
DI++;
CX--;
}
但是在平坦的内存模型中,似乎rep字符串与额外的段一起运行是不正确的。
例如,我创建了一个测试,创建了2个线程,使用rep movs将数组复制到TLS(线程本地存储)数组。逻辑上这不应该起作用,因为TLS数据保存在GS段中,而不是ES中。但是有效。至少看到运行测试的正确结果。 英特尔编译器生成以下代码用于处理。
movl %gs:0, %eax #27.18
movl $1028, %ecx #27.18
movl 32(%esp), %esi #27.18
lea TLS_data1@NTPOFF(%eax), %edi #27.18
movl %ecx, %eax #27.18
shrl $2, %ecx #27.18
rep #27.18
movsl #27.18
movl %eax, %ecx #27.18
andl $3, %ecx #27.18
rep #27.18
movsb #27.18
这里%edi指向TLS数组并且rep movs存储在那里。如果rep mov隐式使用ES偏移(我怀疑),那么这样的代码不应该产生正确的结果。
我在这里想念一下吗?
我创建了测试:
#define _MULTI_THREADED
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMTHREADS 2
#define N 257
typedef struct {
int data1[N];
} threadparm_t;
__thread threadparm_t TLS_data1;
void foo();
void *theThread(void *parm)
{
int rc;
threadparm_t *gData;
pthread_t self = pthread_self();
printf("Thread %u: Entered\n", self);
gData = (threadparm_t *)parm;
TLS_data1 = *gData;
foo();
return NULL;
}
void foo() {
int i;
pthread_t self = pthread_self();
printf("\nThread %u: foo()\n", self/1000000);
for (i=0; i<N; i++) {
printf("%d ", TLS_data1.data1[i]);
}
printf("\n\n");
}
int main(int argc, char **argv)
{
pthread_t thread[NUMTHREADS];
int rc=0;
int i,j;
threadparm_t gData[NUMTHREADS];
printf("Enter Testcase - %s\n", argv[0]);
printf("Create/start threads\n");
for (i=0; i < NUMTHREADS; i++) {
/* Create per-thread TLS data and pass it to the thread */
for (j=0; j < N; j++) {
gData[i].data1[j] = i+1;
}
rc = pthread_create(&thread[i], NULL, theThread, &gData[i]);
}
printf("Wait for the threads to complete, and release their resources\n");
for (i=0; i < NUMTHREADS; i++) {
rc = pthread_join(thread[i], NULL);
}
printf("Main completed\n");
return 0;
}
答案 0 :(得分:0)
你缺少的是这些操作:
movl %gs:0, %eax
...
lea TLS_data1@NTPOFF(%eax), %edi
将线程本地TLS_data1
的从零开始的地址加载到%edi
,这将与零基ES段一起正常工作。