Сборка - потокобезопасные локальные переменные - PullRequest
2 голосов
/ 24 мая 2011

Я пытаюсь использовать потокобезопасные локальные переменные в программе сборки.Я искал в сети, но я не нашел ничего простого.

В настоящее время я использую ассемблер GCC, так как программа представляет собой смесь кода C и ассемблера, но конечная программа будет содержать коддля нескольких платформ / соглашений о вызовах.

На данный момент я объявил свои переменные, используя псевдооперацию .lcomm.Насколько я понимаю, эти переменные будут размещены в разделе .bss.Поэтому я предполагаю, что они будут общими для всех потоков.

Есть ли способ иметь переменные TLS непосредственно в сборке, или мне следует использовать специфичные для платформы реализации, такие как pthread или __declspec вWindows?

Надеюсь, это достаточно ясно.Не стесняйтесь спрашивать, нужна ли дополнительная информация.

Спасибо всем,

РЕДАКТИРОВАТЬ

Вот код вопроса:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp
    movq    %rsp,   %rbp

    xor     %rax,   %rax

    popq    stack0( %rip )
    popq    stack1( %rip )

    callq   *%rdi

    pushq   stack1( %rip )
    pushq   stack0( %rip )

    leave
    ret

Обычно он используется для перенаправления вызова функции C.

Прототип C:

extern uint64_t XSRuntime_CallMethod( void ( *m )( void * self, ... ), ... );

В качестве первого аргумента принимает указатель на функцию, следовательно,callq *%rdi, так как я тестирую это с ABI системы V.

Код сборки очень прост, и я бы хотел оставить его таким, чтобы его можно было легко обслуживать.

Вопрос в том, как сделать переменные stack0 и stack1 потокобезопасными.

Ответы [ 5 ]

1 голос
/ 24 мая 2011

Не так хорошо знаком с ассемблером, так:

.lcomm  stack0, 8
.lcomm  stack1, 8

.globl _XSRuntime_CallMethod
_XSRuntime_CallMethod:

    pushq   %rbp // save BP
    movq    %rsp,   %rbp // load BP with SP

    xor     %rax,   %rax  // clear AX

    popq    stack0( %rip )  // pop return address into STACK0
    popq    stack1( %rip )  // pop flags into stack1

    callq   *%rdi  // call the indirect procedure, so putting flags/return to         XSRuntime_CallMethod onto stack

    pushq   stack1( %rip ) // put caller flags onto stack
    pushq   stack0( %rip ) // put caller return onto stack

    leave // clean passed parameters from stack
    ret   // and back to caller

Так ли это работает, да ??

Если так, не проще ли просто перейти к косвенной процедуре,вместо того, чтобы называть это?Тогда вам не нужны никакие дополнительные переменные для хранения флагов / возврата вызывающей стороны, и косвенная процедура возвращается непосредственно к вызывающей стороне.

Просто предложение - пока я сделал ассемблер.

Если выНужно где-то хранить адрес вызывающего абонента, определять SP, (вводить?) и использовать кадр стека.Что-то еще может быть поточно-небезопасным в какой-то момент.

Rgds, Martin

Ну, с TLS, может быть, не поточно-небезопасным, но как насчет рекурсивных вызовов?Для этого вам понадобится еще один стек в TLS, так что вы также можете использовать стек SP

Martin

1 голос
/ 24 мая 2011

Как вы думаете, компилятор реализует локальные переменные потока?Попробуйте скомпилировать такую ​​программу с помощью -S или / FA, и вы увидите.Подсказка: для получения доступа к хранилищу TLS он должен полагаться на специфичные для ОС API или другие подробности.Иногда этапы подготовки скрыты в CRT, но нет единого способа сделать это.

Например, вот как недавно MSVC делает это:

_TLS    SEGMENT
?number@@3HA DD 01H DUP (?)             ; number
_TLS    ENDS
EXTRN   __tls_array:DWORD
EXTRN   __tls_index:DWORD
_TEXT   SEGMENT
[...]
mov eax, DWORD PTR __tls_index
mov ecx, DWORD PTR fs:__tls_array
mov edx, DWORD PTR [ecx+eax*4]
mov eax, DWORD PTR ?number@@3HA[edx]

Как вы можете видеть, он используетспециальные переменные, которые инициализируются CRT.

В последних версиях Linux GCC может использовать перемещения, специфичные для TLS:

.globl number
    .section    .tbss,"awT",@nobits
number:
    .zero   4
    .text
    [...]
    movl    %gs:number@NTPOFF, %eax

Если вам нужна переносимость, лучше не полагаться на такие ОС.конкретные детали, но используйте общий API, такой как pthread, или используйте подход на основе стека, предложенный Мартином.Но я думаю, если бы вы хотели переносимости, вы бы не использовали ассемблер:)

0 голосов
/ 26 мая 2011

Как упоминалось ранее, локальные переменные (основанные на стеке) по своей природе поточно-ориентированы, поскольку каждый поток имеет свой собственный стек.

Потокобезопасная переменная, достижимая всеми потоками (не основанными на стеке)вероятно, лучше всего реализовать с использованием спин-блокировки (или аналога в ядре Windows NT, критическая секция).Такая переменная должна быть заблокирована перед доступом, доступна, а затем разблокирована.Один из вариантов может заключаться в том, что чтение является бесплатным, но запись должна быть оформлена путем блокировки / разблокировки.

Только компиляторы AFAIK не реализуют поточно-ориентированные переменные.Вместо этого они предоставляют функции lib, которые обращаются к требуемым функциям ОС.

0 голосов
/ 24 мая 2011

Вы, вероятно, должны использовать (делать вызовы) TlsAlloc & TlsFree (или их другие эквиваленты ОС), чтобы сделать что-то подобное. возвращенные индексы могут быть сохранены в глобальном наборе один раз, только для чтения, для простого использования.

В зависимости от того, что содержат переменные и того, что делает код, использующий их, вы можете избежать использования атомарных операций, но это может привести к собственным проблемам.

0 голосов
/ 24 мая 2011

??«Классические» локальные переменные, т.е.параметры / переменные / результаты, к которым получают доступ смещения стека, по своей сути поточно-ориентированы.

Если вам нужен TLS, не зависящий от платформы, передайте некоторый подходящий экземпляр структуры / класса во все потоки, либо в качестве созданияпараметр, в поле потока перед возобновлением всех потоков, первое сообщение в очередь ввода потоков или что-то еще ...

Rgds, Martin

...