Лаборатория Tarantool

Системное программирование

Лекция 6:

Потоки. Отличие от процессов. Атомарные операции. Синхронизация. Атрибуты. Особенности многопоточных процессов. Вид в ядре.

Version: 2

The presentation is outdated and not maintained anymore. Please, refer to the English version for the most actual slides.

Новости

Дедлайны:

  • планировщик корутин: 19 марта, штраф -10
  • shell: 2 апреля

Процесс [1]

0x0

0xffffffff

.text

.data

.bss

.heap

.stack

.env

kernel

0xc0000000

Файловые дескрипторы

Очередь сигналов

IPC

Процесс [2]

Задача

.text

.data

.bss

.stack

.heap

.stack

.heap

Translation Lookaside Buffer

  • Пересылка сообщений
  • Блокировки на общую память
  • Копирования через нее
  • "Убийство" TLB

Поток [1]

Задача

.text

.data

.bss

.stack

.heap

Translation Lookaside Buffer

  • Прямые вызовы функций
  • Прямые обращения к памяти
  • Одно адресное пространство

call()

call()

Поток [2]

struct task_struct {
	struct thread_info		thread_info;
	volatile long			state;
	void				*stack;
	atomic_t			usage;
	unsigned int			cpu;
	int				prio;
	struct mm_struct		*mm;
	int				exit_state;
	int				exit_code;
	int				exit_signal;
	pid_t				pid;
	struct task_struct              *parent;
	struct list_head		children;
	u64				start_time;
	const struct cred		*cred;
	struct files_struct		*files;
	struct thread_struct		thread;
};

В Linux потоки и процессы - одно и то же

Это потоки делят

Clone() [1]

int
clone(int (*fn)(void *), void *child_stack,
      int flags, void *arg, ...);

Создать новый struct task_struct из user space

#define CLONE_CHILD_CLEARTID
#define CLONE_CHILD_SETTID
#define CLONE_FILES
#define CLONE_FS
#define CLONE_IO
#define CLONE_NEWCGROUP
#define CLONE_NEWIPC
#define CLONE_NEWNET
#define CLONE_NEWNS
#define CLONE_NEWPID
#define CLONE_NEWUSER
#define CLONE_NEWUTS
#define CLONE_PARENT
#define CLONE_PARENT_SETTID
#define CLONE_PID
#define CLONE_PTRACE
#define CLONE_SETTLS
#define CLONE_SIGHAND
#define CLONE_STOPPED
#define CLONE_SYSVSEM
#define CLONE_THREAD
#define CLONE_UNTRACED
#define CLONE_VFORK
#define CLONE_VM
CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD

Clone() [2]

int
thread_create_clone(int (*func)(void *), void *arg, void **stack)
{
	int stack_size = 65 * 1024;
	*stack = malloc(stack_size);
	void *stack_top = (char *) *stack + stack_size;
	int flags = CLONE_VM | CLONE_FS | CLONE_FILES |
		    CLONE_SIGHAND | CLONE_THREAD;
	return clone(func, stack_top, flags, arg);
}

stack

stack_top

Чем делиться?

  • CLONE_VM - памятью
  • CLONE_FS - рабочей директорией
  • CLONE_FILES - таблицей файловых дескрипторов
  • CLONE_SIGHAND - обработчиками сигналов
  • CLONE_THREAD - иметь тот же PID

Принимает вершину нового стека

Создал отсюда

Но стек растет вниз - вершина выше

Clone() [3]

volatile bool is_finished = false;

int thread_f(void *arg)
{
	pid_t thread_id = gettid();
	pid_t pid = getpid();
	printf("pid %d, tid %d: new thread, arg = %d\n",
	       (int)pid, (int)thread_id,
               *((int *) arg));
	is_finished = true;
	return 0;
}

int main()
{
	pid_t thread_id = gettid();
	pid_t pid = getpid();
	printf("pid %d, tid %d: main thread\n",
	       (int)pid, (int)thread_id);
	void *stack;
	int arg = 100;
	int ret = thread_create_clone(thread_f,
                                      (void *) &arg,
                                      &stack);
	printf("pid %d, tid %d: clone result = %d\n",
	       (int)pid, (int)thread_id, ret);
	while (! is_finished)
		sched_yield();
	free(stack);
	return 0;
}

Это будет новый поток

Печатаю идентификаторы потока и процесса в обоих потоках

Запускаю поток и жду его завершения

Не забываю освободить стэк

$> gcc 1_clone.c
$> ./a.out

pid 10194, tid 10194:
    main thread

pid 10194, tid 10194:
    clone result = 10195

pid 10194, tid 10195:
    new thread, arg = 100

Одинаковый PID

Разные TID

Зачем ждать завершения?

Clone() [4]

volatile bool is_finished = false;

int thread_f(void *arg)
{
	/* ... */
}

int main()
{
	/* ... */
	while (! is_finished)
		sched_yield();
	free(stack);
	return 0;
}

Что будет, если не ждать завершения созданного потока?

1) стек удалится у него из под ног

2) завершение главного потока - завершение процесса

2 балла

Volatile [1]

Volatile память

Регистры

DRAM,

SRAM

Non-volatile память

Магнитная

Flash

bool value = true;

while (value) {
    /*
     * do not change
     * 'value'
     */
}

gcc

while (true) {
    /* ... */
}
struct object obj;
/* ... */
a = a + obj.value;
/* ... */
b = b + obj.value;
/* ... */
volatile bool value = true;

while (value) {
    /*
     * do not change
     * 'value'
     */
}
volatile bool value = true;

while (value) {
    /*
     * do not change
     * 'value'
     */
}
struct object obj;
int tmp = obj.value;
/* ... */
a = a + tmp;
/* ... */
b = b + tmp;
/* ... */
volatile struct object obj;
/* ... */
a = a + obj.value;
/* ... */
b = b + obj.value;
/* ... */
volatile struct object obj;
/* ... */
a = a + obj.value;
/* ... */
b = b + obj.value;
/* ... */

Volatile [2]

void *
thread_f(void *arg)
{
	*((bool *)arg) = true;
	return NULL;
}

int
main()
{
	const bool is_finished = false;
	pthread_t tid;
	pthread_create(&tid, NULL,
                       thread_f,
		       &is_finished);
	while (! is_finished)
		sched_yield();

	pthread_join(tid, NULL);
	return 0;
}
$> clang 2_non_volatile.c
$> ./a.out

Программа зависает

$> gobjdump -d a.out
<_main>:
100000f40: push   %rbp
100000f41: mov    %rsp,%rbp
100000f44: sub    $0x20,%rsp
100000f48: lea    -0x10(%rbp),%rdi
100000f4c: xor    %eax,%eax
100000f4e: mov    %eax,%esi
100000f50: lea    -0x37(%rip),%rdx
100000f57: lea    -0x5(%rbp),%rcx
100000f5b: movl   $0x0,-0x4(%rbp)
100000f62: movb   $0x0,-0x5(%rbp)
100000f66: callq  100000f7c <_main+0x3c>
100000f6b: mov    %eax,-0x14(%rbp)
100000f6e: callq  100000f82 <_main+0x42>
100000f73: mov    %eax,-0x18(%rbp)
100000f76: jmpq   100000f6e <_main+0x2e>

Вот цикл

В нем безусловный jump

То есть переменную is_finished выкинули

Надо разобраться ...

Volatile [3]

void *
thread_f(void *arg)
{
	*((bool *)arg) = true;
	return NULL;
}

int
main()
{
	const volatile bool
            is_finished = false;
	pthread_t tid;
	pthread_create(&tid, NULL,
                       thread_f,
		       &is_finished);
	while (! is_finished)
		sched_yield();

	pthread_join(tid, NULL);
	return 0;
}
$> clang 3_volatile.c
$> ./a.out
$> # finished
$> gobjdump -d a.out
<_main>:
100000f00: push   %rbp
...
100000f2e: mov    -0x5(%rbp),%al
100000f31: xor    $0xff,%al
100000f33: test   $0x1,%al
100000f35: jne    100000f40 <_main+0x40>
100000f3b: jmpq   100000f4d <_main+0x4d>
100000f40: callq  100000f74 <_main+0x74>
100000f45: mov    %eax,-0x18(%rbp)
100000f48: jmpq   100000f2e <_main+0x2e>
100000f4d: xor    %eax,%eax
100000f4f: mov    %eax,%esi
100000f51: mov    -0x10(%rbp),%rdi
...
100000f66: retq

Цикл изменился

Начало цикла и проверка условия. Прошло - заходим в цикл

Если не прошла, то прыг из цикла

В конце прыг в начало цикла

check_condition:
    if (! is_finished)
        goto do_iteration;
    goto end;

do_iteration:
    sched_yield();
    goto check_condition;

end:

Атомарность [1]

volatile bool is_finished = false;
volatile int counter = 0;

int
thread_f(void *arg)
{
	for (int i = 0; i < 100000; ++i)
		counter = counter + 1;
	is_finished = true;
	return 0;
}

int
main()
{
	void *stack;
	thread_create_clone(thread_f, NULL,
                            &stack);
	for (int i = 0; i < 100000; ++i)
		counter = counter + 1;
	while (! is_finished)
		sched_yield();
	printf("counter = %d\n", counter);
	free(stack);
	return 0;
}
$> gcc 4_not_atomic.c
$> ./a.out
counter = 133664
$> ./a.out
counter = 200000
$> ./a.out
counter = 127934
$> ./a.out
counter = 140386

Атомарность [2]

counter = counter + 1;

gcc

mov counter,%eax
add $0x1,%eax
mov %eax,counter

Загрузить из памяти -

Увеличить -

Положить в память -

Поток 1

Поток 2

mov counter, $eax
mov counter,%eax
add $0x1,%eax
mov %eax,counter
add $0x1,%eax
mov %eax,counter

Увеличивает старое значение

Атомарность [3]

__sync_add_and_fetch(&counter, 1);

gcc

lock addl $0x1,counter
type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)

type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)

type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)
void __atomic_load (type *ptr, type *ret, int memorder)
void __atomic_store (type *ptr, type *val, int memorder)

void __atomic_exchange (type *ptr, type *val, type *ret, int memorder)
bool __atomic_compare_exchange (type *ptr, type *expected, type *desired, ...)

type __atomic_add_fetch (type *ptr, type val, int memorder)
type __atomic_sub_fetch (type *ptr, type val, int memorder)

type __atomic_fetch_add (type *ptr, type val, int memorder)
type __atomic_fetch_sub (type *ptr, type val, int memorder)

Атомарность [4]

volatile bool is_finished = false;
volatile int counter = 0;

int
thread_f(void *arg)
{
	for (int i = 0; i < 100000; ++i)
		__sync_add_and_fetch(&counter, 1);
	is_finished = true;
	return 0;
}

int
main()
{
	void *stack;
	thread_create_clone(thread_f, NULL,
                            &stack);
	for (int i = 0; i < 100000; ++i)
		__sync_add_and_fetch(&counter, 1);
	while (! is_finished)
		sched_yield();
	printf("counter = %d\n", counter);
	free(stack);
	return 0;
}
$> gcc 5_atomic.c
$> ./a.out
counter = 200000
$> ./a.out
counter = 200000
$> ./a.out
counter = 200000
volatile bool is_finished = false;
volatile int counter = 0;

int
thread_f(void *arg)
{
	for (int i = 0; i < 100000; ++i)
		counter = counter + 1;
	is_finished = true;
	return 0;
}

int
main()
{
	void *stack;
	thread_create_clone(thread_f, NULL,
                            &stack);
	for (int i = 0; i < 100000; ++i)
		counter = counter + 1;
	while (! is_finished)
		sched_yield();
	printf("counter = %d\n", counter);
	free(stack);
	return 0;
}

Атомарность [5]

$> clang 6_bad_lock.c
struct complex_thing {
	bool lock;
	char string[128];
};

volatile struct complex_thing thing;
volatile bool start = false;

void *thread_f(void *arg)
{
	int id = (int) arg;
	while (! start) {};
	if (! thing.lock) {
		thing.lock = true;
		for (int i = 0; i < 127; ++i)
			thing.string[i] = 'a' + id;
	}
	return NULL;
}

int main()
{
	pthread_t tid[6];
	for (int i = 0; i < 6; ++i) {
		pthread_create(&tid[i], NULL, thread_f,
                               (void *) i);
        }
	start = true;
	for (int i = 0; i < 6; ++i)
		pthread_join(tid[i], NULL);
	printf("%s\n", thing.string);
	return 0;
}
$> ./a.out
bbaaaaaaaaaaaaaaaaaaaaaa
bbbbbbaaabbaaaaaaaaabaaa
aabaaaabbbabbbbbbbbaaaaa
aaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaa

Между этими строками может вклиниться другой поток

Атомарность [6]

$> clang 6_bad_lock.c
struct complex_thing {
	bool lock;
	char string[128];
};

volatile struct complex_thing thing;
volatile bool start = false;

void *thread_f(void *arg)
{
	int id = (int) arg;
	while (! start) {};
	if (! thing.lock) {
		thing.lock = true;
		for (int i = 0; i < 127; ++i)
			thing.string[i] = 'a' + id;
	}
	return NULL;
}

int main()
{
	pthread_t tid[6];
	for (int i = 0; i < 6; ++i) {
		pthread_create(&tid[i], NULL, thread_f,
                               (void *) i);
        }
	start = true;
	for (int i = 0; i < 6; ++i)
		pthread_join(tid[i], NULL);
	printf("%s\n", thing.string);
	return 0;
}
$> ./a.out
aaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaa

$> ./a.out
ffffffffffffffffffffffff
ffffffffffffffffffffffff
ffffffffffffffffffffffff
ffffffffffffffffffffffff
ffffffffffffffffffffffff
fffffff
struct complex_thing {
	bool lock;
	char string[128];
};

volatile struct complex_thing thing;
volatile bool start = false;

void *thread_f(void *arg)
{
	int id = (int) arg;
	while (! start) {};
	if (__sync_bool_compare_and_swap(&thing.lock, 0, 1)) {
		for (int i = 0; i < sizeof(thing.string) - 1; ++i)
			thing.string[i] = 'a' + id;
		__sync_bool_compare_and_swap(&thing.lock, 1, 0);
	}
	return NULL;
}

int main()
{
	pthread_t tid[6];
	for (int i = 0; i < 6; ++i) {
		pthread_create(&tid[i], NULL, thread_f,
                               (void *) i);
        }
	start = true;
	for (int i = 0; i < 6; ++i)
		pthread_join(tid[i], NULL);
	printf("%s\n", thing.string);
	return 0;
}

Spinlock

void
spin_lock(volatile bool *lock)
{
    while (! __sync_bool_compare_and_swap(lock, 0, 1))
    {}
}

void
spin_unlock(volatile bool *lock)
{
    __sync_bool_compare_and_swap(lock, 1, 0);
}

Спинлок - блокировка средствами процессора, без прерываний

Разблокируется быстро - не потеряются кеши и регистры

Не разблокируется быстро - впустую потрачено время процессора

Futex [1]

Fast User-space Mutex

Mutex - не базовое понятие в Linux. Futex - базовое.

Оркестрация через ядро

int
futex(int *uaddr, int futex_op, int val,
      const struct timespec *timeout,
      int *uaddr2, int val3);

Futex [2]

int
futex(int *uaddr, int futex_op, int val,
      const struct timespec *timeout,
      int *uaddr2, int val3);

Futex - это просто значение int

Поток изъявляет желание узнать, когда это значение станет != val

futex(&value, FUTEX_WAIT, wait_not_for);

Другой поток может уведомить об изменении 1 и более других потоков

futex(&value, FUTEX_WAKE, n_to_wake);

Futex [3]

Поток 1

Поток 2

int value = 100;
futex(&value, FUTEX_WAIT, 100,
      NULL, NULL, 0);
/* sleep ... */

Поток засыпает и хочет проснуться, когда value будет != 100

value = 0;
futex(&value, FUTEX_WAKE, 1,
      NULL, NULL, 0);

Другой поток меняет значение на 0 и уведомляет ждущих

value != 100;
/* continue ... */

Ядро пробуждает поток

Futex [4]

int
futex_wait(int *futex, int val)
{
	return syscall(SYS_futex, futex, FUTEX_WAIT, val,
		       NULL, NULL, 0);
}

int
futex_wake(int *futex)
{
	return syscall(SYS_futex, futex, FUTEX_WAKE, 1,
		       NULL, NULL, 0);
}

void
futex_lock(int *futex)
{
	while (__sync_val_compare_and_swap(futex, 0, 1) != 0)
		futex_wait(futex, 1);
}

void
futex_unlock(int *futex)
{
	__sync_bool_compare_and_swap(futex, 1, 0);
	futex_wake(futex);
}

Библиотечной обертки нет - нужно звать syscall напрямую

Futex сам значение не меняет - это нужно делать руками, причем атомарно

Fast User-space Mutex - потому что первая попытка не в ядре

Разблокировать так же

Будем хранить всего два значения - 0 и 1, как "свободно" и "занято"

Futex [5]

volatile int counter = 0;
volatile bool start = false;
volatile int futex = 0;

void *thread_f(void *futex)
{
	while (! start) {};

	for (int i = 0; i < 100000; ++i) {
		futex_lock((int *) futex);
		counter = counter + 1;
		futex_unlock((int *) futex);
	}
	return NULL;
}

int main()
{
	pthread_t tid[6];
	int futex = 0;
	for (int i = 0; i < 6; ++i) {
		pthread_create(&tid[i], NULL, thread_f,
                               (void *) &futex);
        }
	start = true;
	for (int i = 0; i < 6; ++i)
		pthread_join(tid[i], NULL);
	printf("%d\n", counter);
	return 0;
}
$> gcc 8_futex.c -pthread
$> ./a.out
600000
$> ./a.out
600000
$> ./a.out
600000
$> ./a.out
600000

Futex [6]

void
futex_lock(int *futex)
{
	while (__sync_val_compare_and_swap(futex, 0, 1) != 0)
		futex_wait(futex, 1);
}

void
futex_unlock(int *futex)
{
	__sync_bool_compare_and_swap(futex, 1, 0);
	futex_wake(futex);
}

Как можно избежать futex_wake() в unlock(), если никто не ждет на futex_wait()?

Защитить весь futex спинлоком, и добавить счетчик ждущих. Если он 0 на unlock, то не делать futex_wake().

2 балла

Барьеры памяти [1]

read a

write b

read c

Суть проблемы:

Исходный код:

Реальное выполнение:

write e

read a

write b

read c

write e

> code

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

Барьеры памяти [2]. Простая ошибка

Переупорядочивание на CPU значит, что так делать нельзя:

while (! is_ready)
	usleep(10000);
printf("%d\n", a);
a = 200;
is_ready = true;
a = 100

Поток 1

Поток 2

На экран может быть выведено как 100, так и 200. Поток 1 может увидеть is_ready=true раньше, чем увидит a=200. Из-за переупорядочивания.

Без защиты потоки могут видеть друг друга абсолютно как угодно.

Барьеры памяти [3]. Сложная ошибка

X = 1
r1 = Y

Поток 1

Y = 1
r2 = X

Поток 2

X = 0
Y = 0

На самом деле в конце r1 и r2 могут стать оба равны 0, даже на x86. Команды выше раскладываются на инструкции:

write(X, 1)
read(register, Y)
write(r1, register)

Поток 1

Поток 2

write(Y, 1)
read(register, X)
write(r2, register)

Эти чтения могут завершиться первыми

Логично, что в конце либо r1, либо r2, либо оба должны быть = 1

Барьеры памяти [4]. В теории

LoadLoad

LoadStore

StoreStore

StoreLoad

read(B);
     write(A);
A = B;
C = D;
read(D);
     write(C);
read(B);
     write(A);
     read(D);
write(C);
read(B);
     write(A);
read(D);
     write(C);
read(B);
     write(A);
     read(D);
write(C);

Барьер

Барьер

Барьер

Барьер

Барьеры памяти [5]. На практике

StoreStore

StoreLoad

LoadLoad

LoadStore

Acquire

Release

Реальных барьеров 3:

  • Acquire (Acquire-Read)
  • Release (Release-Write)
  • Full

Full

Барьеры памяти [6]. Acquire-Release

Acquire: LoadLoad + LoadStore - непреодолим "вверх"

Release: LoadStore + StoreStore - непреодолим "вниз"

while (! read_acquire(is_ready))
	usleep(10000);

printf("%d\n", a);
a = 200;


write_release(&is_ready, true);
a = 100

Поток 1

Поток 2

Acquire не даст переменной a прочитаться до is_ready.

Release не даст переменной a записаться после is_ready.

Барьеры памяти [7]. Применение

Инструкции с полным барьером

type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)

type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)

type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)
void __atomic_load (type *ptr, type *ret, int memorder)
void __atomic_store (type *ptr, type *val, int memorder)

void __atomic_exchange (type *ptr, type *val, type *ret, int memorder)
bool __atomic_compare_exchange (type *ptr, type *expected, type *desired, ...)

type __atomic_add_fetch (type *ptr, type val, int memorder)
type __atomic_sub_fetch (type *ptr, type val, int memorder)

type __atomic_fetch_add (type *ptr, type val, int memorder)
type __atomic_fetch_sub (type *ptr, type val, int memorder)

Инструкции с выбором барьера

__ATOMIC_RELAXED, __ATOMIC_CONSUME, __ATOMIC_ACQUIRE,
__ATOMIC_RELEASE, __ATOMIC_ACQ_REL, __ATOMIC_SEQ_CST
memorder:

Барьеры памяти [8]. Spinlock

void
spin_lock(volatile bool *lock)
{
    while (! __atomic_test_and_set(lock, __ATOMIC_ACQUIRE))
    {}
}

void
spin_unlock(volatile bool *lock)
{
    __atomic_clear(lock, __ATOMIC_RELEASE);
}

Оптимальная версия спинлока, без полных барьеров

Работает в ~1.5 раза быстрее предыдущей с 

__sync_bool_compare_and_swap

Переносимость

clone();
futex();

Linux-specific

__sync_bool_compare_and_swap();
__sync_val_compare_and_swap();
__sync_add_and_fetch();
/* ... */

GCC-specific

Mac? FreeBSD? ...

clang? MinGW? MSVC? ...

pthread [1]

pthread_create();
pthread_join();
pthread_exit();
pthread_mutex_lock();
pthread_mutex_unlock();
/* ... */

GCC на Linux

futex();
clone();
__sync_*();

GCC на Mac

?

MinGW на Windows

?

pthread [2]

int
pthread_create(pthread_t *thread, const pthread_attr_t *attr,
               void *(*start_routine) (void *), void *arg);

pthread_t - дескриптор потока в библиотеке pthread

Функция для запуска в отдельном потоке

struct pthread_t {
        int lock;
        pid_t tid;
        void *func_result;
        void *stack;
        size_t stack_size;
};

Защита структуры

Идентификатор потока

Сохраненный до востребования результат функции

Стек для clone(), который pthread сам освободит

pthread [3]

volatile bool is_finished = false;

volatile pthread_t child_id;

int
thread_f(void *arg)
{
	child_id = pthread_self();
	is_finished = true;
	return 0;
}

int
main()
{
	void *stack;
	thread_create_clone(thread_f, NULL, &stack);

	while (! is_finished)
		sched_yield();
	
	pthread_join(child_id, NULL);
	free(stack);
	return 0;
}
$> gcc 9_clone_vs_pthread.c \
    -pthread
$> ./a.out
$> # finished ok

Функция из pthread зовется в потоке, созданном не через pthread_сreate()

Ресурсы освобождаются через pthread_join() - уничтожается pthread_t

pthread [4]

int
pthread_join(pthread_t thread, void **retval);
{
        while (! thread.is_finished) {};
        free(thread.stack);
        *retval = thread.func_value;
        return 0;
}

pthread [5]

typedef int (*cthread_f)(void *);

struct cthread {
	int returned_code;
	cthread_f func;
	void *arg;
	void *stack;
	bool is_finished;
};

int cthread_runner(void *arg)
{
	struct cthread *thread =
                (struct cthread *) arg;
	thread->returned_code =
                thread->func(thread->arg);
	thread->is_finished = true;
	return 0;
}
void cthread_create(struct cthread *result,
                    cthread_f func, void *arg)
{
	result->returned_code = 0;
	result->func = func;
	result->arg = arg;
	result->is_finished = false;
	thread_create_clone(cthread_runner,
                            (void *) result,
			    &result->stack);
}

int cthread_join(volatile struct
                 cthread *thread)
{
	while (! thread->is_finished)
		sched_yield();
	free(thread->stack);
	return thread->returned_code;
}

Дескриптор потока

Стек и код возврата для cthread_join

Запускает поток, но с другой функцией - зачем?

Потому что пользовательская функция сама не сохранит результат в cthread, и не выставит флаг завершения

pthread [6]

int
func(void *arg)
{
	printf("arg = %d\n", *((int *) arg));
	return 200;
}

int
main()
{
	struct cthread thread;
	int arg = 100;
	cthread_create(&thread, func, (void *) &arg);
	int retcode = cthread_join(&thread);
	printf("thread is joined with retcode = %d\n", retcode);
	return 0;
}
$> gcc 10_cthreads_1.c
$> ./a.out
arg = 100
thread is joined with retcode = 200

pthread [7]

void
pthread_exit(void *retval);

Как реализован pthread_exit?

setjmp()/longjmp()

1 балл

pthread [8]

typedef int (*cthread_f)(void *);

struct cthread {
	int returned_code;
	cthread_f func;
	void *arg;
	void *stack;
	bool is_finished;
};


int cthread_runner(void *arg)
{
	struct cthread *thread =
                (struct cthread *) arg;
	thread->returned_code =
                thread->func(thread->arg);
	thread->is_finished = true;
	return 0;
}
void cthread_create(struct cthread *result,
                    cthread_f func, void *arg)
{
	result->returned_code = 0;
	result->func = func;
	result->arg = arg;
	result->is_finished = false;
	thread_create_clone(cthread_runner,
                            (void *) result,
			    &result->stack);
}

int cthread_join(volatile struct
                 cthread *thread)
{
	while (! thread->is_finished)
		sched_yield();
	free(thread->stack);
	return thread->returned_code;
}
typedef int (*cthread_f)(void *);

struct cthread {
	int returned_code;
	cthread_f func;
	void *arg;
	void *stack;
	bool is_finished;
	jmp_buf jmp;
};

int cthread_runner(void *arg)
{
	struct cthread *thread =
                (struct cthread *) arg;
        if (setjmp(thread->jmp) == 0) {
                arg = thread->arg;
		thread->returned_code =
			thread->func(arg);
	}
	thread->is_finished = true;
	return 0;
}
void cthread_exit(struct cthread *thread,
                  int retcode)
{
	thread->returned_code = retcode;
	longjmp(thread->jmp, 1);
}

Позиция стека запоминается в самом начале, и на нее можно прыгнуть в любой момент

pthread [9]

int
pthread_detach(pthread_t thread);

Чтобы не нужно было делать pthread_join(). Ресурсы освободятся сами, когда поток завершится.

pthread [10]

typedef int (*cthread_f)(void *);

struct cthread {
	int returned_code;
	cthread_f func;
	void *arg;
	void *stack;
	bool is_finished;
	bool is_detached;
	jmp_buf jmp;
};

void cthread_destroy(struct cthread *thread)
{
	printf("thread is destroyed\n");
	free(thread->stack);
}

int cthread_runner(void *arg)
{
	struct cthread *thread =
                (struct cthread *) arg;
	if (setjmp(thread->jmp) == 0) {
                arg = thread->arg;
		thread->returned_code =
			thread->func(arg);
	}
	if (thread->is_detached)
		cthread_destroy(thread);
	thread->is_finished = true;
	return 0;
}
void cthread_detach(struct cthread *thread)
{
	if (thread->is_finished)
		cthread_destroy(thread);
	thread->is_detached = true;
}

void cthread_create(struct cthread *result,
                    cthread_f func, void *arg)
{
	result->returned_code = 0;
	result->func = func;
	result->arg = arg;
	result->is_finished = false;
	result->is_detached = false;
	thread_create_clone(cthread_runner,
                            (void *) result,
			    &result->stack);
}

Добавился флаг is_detached

pthread [11]

void cthread_detach(struct cthread *thread)
{
	if (thread->is_finished)
		cthread_destroy(thread);
	thread->is_detached = true;
}
void cthread_destroy(struct cthread *thread)
{
	printf("thread is destroyed\n");
	free(thread->stack);
}

int cthread_runner(void *arg)
{
	/* ... */
	if (thread->is_detached)
		cthread_destroy(thread);
	thread->is_finished = true;
	return 0;
}

Какие здесь две ошибки?

1) Стек освобождается прямо в cthread_runner, который на этом же стеке и работает прямо сейчас.

2) Флаги is_detached и is_finished используются не атомарно.

1 балл за каждый

pthread [12]

int
cthread_runner(void *arg)
{
	struct cthread *thread = (struct cthread *) arg;
	if (setjmp(thread->jmp) == 0) {
		thread->returned_code =
			thread->func(thread->arg);
	}
	if (thread->is_detached)
		cthread_destroy(thread);
	thread->is_finished = true;
	return 0;
}

Здесь нигде нельзя удалить свой стек - даже return его использует

struct cthread_stack {
	void *stack;
	struct cthread_stack *next;
};

Можно удалить стек не сразу, а отложить в список, пока поток не завершится. Но как понять, завершился ли поток в ядре?

pthread [13]

static inline int
thread_create_clone_tid(int (*func)(void *), void *arg, void **stack,
			pid_t *tid)
{
	int stack_size = 65 * 1024;
	*stack = malloc(stack_size);
	void *stack_top = (char *) *stack + stack_size;
	int flags = CLONE_VM | CLONE_FS | CLONE_FILES |
		    CLONE_SIGHAND | CLONE_THREAD |
                    CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID;
	return clone(func, stack_top, flags, arg, NULL, NULL, tid);
}

В clone() можно попросить занулить значение по адресу, когда поток совсем завершится

struct cthread_stack {
	pid_t tid;
	void *stack;
	struct cthread_stack *next;
};

С каждым стеком ассоциируем число, которое завершенный clone() будет занулять

pthread [14]

struct cthread {
	int returned_code;
	cthread_f func;
	void *arg;
	void *stack;
	bool is_finished;
	bool is_detached;
	jmp_buf jmp;
};

void cthread_destroy(struct cthread *thread)
{
	printf("thread is destroyed\n");
	free(thread->stack);
}

void cthread_detach(struct cthread *thread)
{
	if (thread->is_finished)
		cthread_destroy(thread);
        sleep(1);
	thread->is_detached = true;
}

int func(void *arg)
{
	printf("thread started\n");
	return 0;
}
int main()
{
	struct cthread thread;
	cthread_create(&thread, func, NULL);
	cthread_detach(&thread);
	while (! thread.is_finished)
		sched_yield();
	printf("detached thread finished\n");
	return 0;
}
$> gcc 11_bad_detach.c
$> ./a.out
thread started
detached thread finished

Стек не освобожден

pthread [15]

struct cthread_stack {
	pid_t tid;
	void *stack;
	struct cthread_stack *next;
};

struct cthread_stack *stack_list = NULL;
volatile bool last_stack_lock = false;

struct cthread {
	int returned_code;
	cthread_f func;
	void *arg;
	struct cthread_stack *stack;
	bool lock;
	bool is_finished;
	bool is_detached;
	jmp_buf jmp;
};
void cthread_destroy(struct cthread *thread)
{
	printf("thread is destroyed\n");
	spin_lock(&last_stack_lock);
	struct cthread_stack *iter = stack_list;
	while (iter != NULL) {
		if (iter->tid != 0)
			break;
		struct cthread_stack *next =
                        iter->next;
		free(iter->stack);
		free(iter);
		iter = next;
		printf("a stack is freed\n");
	}
	thread->stack->next = iter;
	stack_list = thread->stack;
	spin_unlock(&last_stack_lock);
}

Список стеков и лок для его защиты. У каждого cthread на отдельной памяти свой стек.

При уничтожении пытаемся чистить список стеков

Те, у кого tid != 0, еще используются - их не трогать

Положить свой стек в список

int cthread_runner(void *arg)
{
	struct cthread *thread =
                (struct cthread *) arg;
	if (setjmp(thread->jmp) == 0) {
                arg = thread->arg;
		thread->returned_code =
			thread->func(arg);
	}
	spin_lock(&thread->lock);
	if (thread->is_detached)
		cthread_destroy(thread);
	thread->is_finished = true;
	spin_unlock(&thread->lock);
	return 0;
}

Операции с флагами is_detached, is_finished теперь под локом

void cthread_detach(struct cthread *thread)
{
	spin_lock(&thread->lock);
	if (thread->is_finished)
		cthread_destroy(thread);
	thread->is_detached = true;
	spin_unlock(&thread->lock);
}

Тоже самое

void cthread_create(struct cthread *result, cthread_f func,
	            void *arg)
{
	result->returned_code = 0;
	result->func = func;
	result->arg = arg;
	result->is_finished = false;
	result->is_detached = false;
	result->lock = false;
	result->stack =
                malloc(sizeof(*result->stack));
	result->stack->next = NULL;
        void **stack_p = &result->stack->stack;
        pid_t *tid_p = &result->stack->tid;
	thread_create_clone_tid(cthread_runner,
                                result,
                                stack_p,
                                tid_p);
}

struct cthread_stack создается на куче, чтобы он мог пережить завершение потока. В нем ядро обновит tid, когда clone() завершится.

pthread [16]

int func(void *arg)
{
	printf("thread started\n");
	return 0;
}

int main()
{
	struct cthread thread[10];
	for (int i = 0; i < 10; ++i) {
		cthread_create(&thread[i], func,
                               NULL);
		cthread_detach(&thread[i]);
	}
	for (int i = 0; i < 10; ++i) {
		if (! thread[i].is_finished) {
			i = 0;
			sched_yield();
		}
	}
	printf("detached threads finished\n");
	return 0;
}
$> gcc 12_cthread_detach.c
$> ./a.out
thread started
thread is destroyed
thread started
thread is destroyed
a stack is freed
thread started
thread is destroyed
a stack is freed
thread started
thread is destroyed
a stack is freed
thread started
thread is destroyed
a stack is freed
thread started
thread is destroyed
a stack is freed
thread started
thread is destroyed
a stack is freed
thread started
thread is destroyed
a stack is freed
thread started
thread is destroyed
a stack is freed
thread started
thread is destroyed
a stack is freed
detached threads finished

pthread [17]

pthread_create();
pthread_join();
pthread_detach();

pthread [18]

int
pthread_mutex_destroy(pthread_mutex_t *mutex);

int
pthread_mutex_init(pthread_mutex_t *mutex,
                   const pthread_mutexattr_t *attr);

int
pthread_mutex_lock(pthread_mutex_t *mutex);

int
pthread_mutex_trylock(pthread_mutex_t *mutex);

int
pthread_mutex_unlock(pthread_mutex_t *mutex);

int
pthread_mutex_timedlock(pthread_mutex_t *mutex,
                        const struct timespec *abs_timeout); 

На Linux все через futex и атомарные команды процессора

pthread [18]

int
pthread_mutex_trylock(pthread_mutex_t *mutex);

Как реализован pthread_mutex_trylock?

{
        return __sync_bool_compare_and_swap(mutex->lock, 0, 1);
}

1 балл

pthread [19]

int
pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

int
pthread_rwlock_init(pthread_rwlock_t *rwlock,
                    const pthread_rwlockattr_t *attr);

int
pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int
pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

int
pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock,
                           const struct timespec *abs_timeout);

int
pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock,
                           const struct timespec *abs_timeout);

int
pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

int
pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int
pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

Сколько угодно блокировок на чтение

Только одна на запись

На rdlock() и wrlock() один unlock()

pthread [20]

int
pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int
pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int
pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

Почему unlock() один? Почему нет rdunlock(), rwunlock()?

/* Possible implementation. */
{
        if (mutex->readers > 0)
                mutex->readers--;
        else
                mutex->writers = 0;
}

2 балла

pthread [21]

struct condvar {
	pthread_mutex_t event_lock;
	bool is_event_set;
};

void condvar_signal(struct condvar *condvar)
{
	printf("condvar signaled\n");
	condvar->is_event_set = true;
	pthread_mutex_unlock(&condvar->event_lock);

	pthread_mutex_lock(&condvar->event_lock);
	condvar->is_event_set = false;
	printf("condvar is locked\n");
}

void condvar_wait(struct condvar *condvar)
{
        pthread_mutex_t *lock =
                &condvar->event_lock;
	for (;;) {
		pthread_mutex_lock(lock);
		if (condvar->is_event_set) {
			condvar->is_event_set =
                                false;
			pthread_mutex_unlock(lock);
			return;
		}
		pthread_mutex_unlock(lock);
	}
}
void condvar_create(struct condvar *condvar)
{
        pthread_t *lock =
                &condvar->event_lock;
	pthread_mutex_init(lock, NULL);
	pthread_mutex_lock(lock);
	condvar->is_event_set = false;
}

Простой condition variable - это флаг события, защищенный мьютексом

При инициализации лок захватывается генератором событий

Ожидающие тоже пытаются взять лок, но блокируются - лок уже взят

Генератор событий при появлении события отпускает лок, и один из ждущих просыпается

pthread [22]

struct condvar condvar;

void *
thread_f(void *arg)
{
	int id = (int) arg;
	for (;;) {
		condvar_wait(&condvar);
		printf("%d processed the event\n",
                       id);
	}
}

int
main()
{
	condvar_create(&condvar);
	const int thread_count = 10;
	pthread_t tid[thread_count];
	for (int i = 0; i < thread_count; ++i) {
		pthread_create(&tid[i], NULL,
                               thread_f,
                               (void *) i);
        }
	while (getchar() != EOF)
		condvar_signal(&condvar);
	for (int i = 0; i < thread_count; ++i)
		pthread_join(tid[i], NULL);
	return 0;
}
$> # Mac
$> clang 13_simple_condvar.c
$> ./a.out

condvar signaled
0 processed the event
condvar is locked

condvar signaled
1 processed the event
condvar is locked

condvar signaled
1 processed the event
condvar is locked

$> # Linux
$> gcc 13_simple_condvar.c \
    -pthread
$> ./a.out

condvar signaled
condvar is locked

condvar signaled
condvar is locked

condvar signaled
condvar is locked

condvar signaled
condvar is locked

pthread [23]

struct condvar {
	pthread_mutex_t event_lock;
	bool is_event_set;
	int wait_count;
};

void
condvar_signal(struct condvar *condvar)
{
        int *ptr = &condvar->wait_count;
        pthread_mutex_t *lock =
                &condvar->event_lock;
	int wait_count =
                __sync_fetch_and_add(ptr,
                                     0);
	printf("wait_count = %d\n",
               wait_count);
	if (wait_count == 0) {
		printf("no waiters\n");
		return;
	}
	printf("condvar signaled\n");
	condvar->is_event_set = true;
	pthread_mutex_unlock(lock);

	while (condvar->is_event_set) {}

	pthread_mutex_lock(lock);
	condvar->is_event_set = false;
	printf("condvar is locked\n");
}
void
condvar_wait(struct condvar *condvar)
{
        int *wc = &condvar->wait_count;
        pthread_mutex_t *lock = &condvar->event_lock;
	__sync_add_and_fetch(wc, 1);
	for (;;) {
		pthread_mutex_lock(lock);
		if (condvar->is_event_set) {
			condvar->is_event_set = false;
			__sync_sub_and_fetch(wc, 1);
			pthread_mutex_unlock(lock);
			return;
		}
		pthread_mutex_unlock(lock);
	}
}

void
condvar_create(struct condvar *condvar)
{
        pthread_mutex_t *lock =
                &condvar->event_lock;
	pthread_mutex_init(lock, NULL);
	pthread_mutex_lock(lock);
	condvar->is_event_set = false;
	condvar->wait_count = 0;
}

Добавился wait_count

pthread [24]

struct condvar condvar;

void *
thread_f(void *arg)
{
	int id = (int) arg;
	for (;;) {
		condvar_wait(&condvar);
		printf("%d processed the"\
                       " event\n", id);
		usleep(100000);
	}
}

int
main()
{
	condvar_create(&condvar);
	const int thread_count = 10;
	pthread_t tid[thread_count];
	for (int i = 0; i < thread_count; ++i) {
		pthread_create(&tid[i], NULL,
                               thread_f, i);
        }
	while (getchar() != EOF)
		condvar_signal(&condvar);
	for (int i = 0; i < thread_count; ++i)
		pthread_join(tid[i], NULL);
	return 0;
}
$> gcc 14_condvar.c \
    -pthread
$> ./a.out
wait_count = 10
condvar signaled
2 processed the event
condvar is locked
asdfghjklqwert
wait_count = 10
condvar signaled
4 processed the event
condvar is locked
wait_count = 9
condvar signaled
3 processed the event
condvar is locked
wait_count = 8
...
wait_count = 2
condvar signaled
1 processed the event
condvar is locked
wait_count = 1
condvar signaled
condvar is locked
2 processed the event
wait_count = 0
no waiters
wait_count = 0
no waiters

pthread [25]

int
pthread_cond_destroy(pthread_cond_t *cond);

int
pthread_cond_init(pthread_cond_t *cond,
                  const pthread_condattr_t *attr);

int
pthread_cond_broadcast(pthread_cond_t *cond);

int
pthread_cond_signal(pthread_cond_t *cond);

int
pthread_cond_timedwait(pthread_cond_t *cond,
                       pthread_mutex_t *mutex,
                       const struct timespec *abstime);

int
pthread_cond_wait(pthread_cond_t *cond,
                  pthread_mutex_t *mutex); 

pthread [26]

int
pthread_barrier_destroy(pthread_barrier_t *barrier);

int
pthread_barrier_init(pthread_barrier_t *barrier,
                     const pthread_barrierattr_t *attr,
                     unsigned count);

int
pthread_barrier_wait(pthread_barrier_t *barrier);

N

pthread [27]

static __thread int a = -1;

volatile int ready_to_print = 0;
volatile bool print = false;

void *
thread_f(void *arg)
{
	a = (int) arg;
	__sync_fetch_and_add(&ready_to_print, 1);
	while (! print) {}
	printf("a = %d\n", a);
	return NULL;
}

int
main()
{
	pthread_t t1, t2;
	a = 0;
	pthread_create(&t1, NULL, thread_f, 1);
	pthread_create(&t2, NULL, thread_f, 2);
	while (__sync_fetch_and_add(&ready_to_print,
                                    0) != 2) {}
	print = true;
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	printf("main a = %d\n", a);
	return 0;
}
$> gcc 15_gcc_thread.c \
    -pthread
$> ./a.out
a = 2
a = 1
main a = 0

Копия этой переменной создается для каждого потока

pthread [28]

.text

.data

.bss

.heap

.stack

.env

kernel

.tdata

+

$> gcc 15_gcc_thread.c \
    -pthread
$> objdump -s a.out
...
Contents of section .tdata:
 200d8c ffffffff
...
int
pthread_create(pthread_t *thread, const pthread_attr_t *attr,
               void *(*start_routine) (void *), void *arg)
{
        copy_tdata();
        /* ... */

pthread [29]

void *
pthread_getspecific(pthread_key_t key);

int
pthread_setspecific(pthread_key_t key,
                    const void *value);

int
pthread_key_create(pthread_key_t *key,
                   void (*destructor)(void*));

pthread [30]

volatile int ready_to_print = 0;
volatile bool print = false;

pthread_key_t key;

void *
thread_f(void *arg)
{
	pthread_setspecific(key, arg);
	__sync_fetch_and_add(&ready_to_print, 1);
	while (! print) {}
	int tmp = (int) pthread_getspecific(key);
	printf("value = %d\n", tmp);
	return NULL;
}

int
main()
{
	pthread_t t1, t2;
	pthread_key_create(&key, NULL);
	pthread_setspecific(key, (const void *) 0);

	pthread_create(&t1, NULL, thread_f, (void *) 1);
	pthread_create(&t2, NULL, thread_f, (void *) 2);
	while (__sync_fetch_and_add(&ready_to_print, 0) != 2) {}
	print = true;
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	int tmp = (int) pthread_getspecific(key);
	printf("main value = %d\n", tmp);
	return 0;
}
$> gcc 16_pthread_key.c \
    -pthread
$> ./a.out
value = 2
value = 1
main value = 0

Ключ у всех одинаковый

Но выставляют разные значения в разных потоках

У каждого потока своя версия значения

pthread [31]

volatile bool finish = false;
volatile bool ptr_is_set = false;
volatile int *a_ptr = NULL;

void *
thread_f(void *arg)
{
	int a = 100;
	printf("child stack top = %p\n", &a);
	a_ptr = &a;
	ptr_is_set = true;
	while (! finish) {}
	return NULL;
}

int
main()
{
	pthread_t t;
	printf("main stack top = %p\n", &t);
	pthread_create(&t, NULL, thread_f, NULL);
	while (! ptr_is_set) {}
	printf("foreign a = %d\n", *a_ptr);
	finish = true;
	pthread_join(t, NULL);
	return 0;
}
$> # Linux
$> gcc 17_thread_stacks.c \
    -pthread
$> ./a.out
main stack top = 0x7ffeffb1fe90
child stack top = 0x7f0c9de9cee4
foreign a = 100

$> # Mac
$> clang 17_thread_stacks.c
$> ./a.out
main stack top = 0x7ffee3a53aa0
child stack top = 0x70000521bee4
foreign a = 100

pthread [32]

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

int
pthread_attr_init(pthread_attr_t *attr);

int
pthread_attr_destroy(pthread_attr_t *attr);

int
pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

int
pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);

int
pthread_attr_setstack(pthread_attr_t *attr,
                      void *stackaddr, size_t stacksize);

int
pthread_attr_getstack(const pthread_attr_t *attr,
                      void **stackaddr, size_t *stacksize);

int
pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

int
pthread_attr_getdetachstate(const pthread_attr_t *attr,
                            int *detachstate);

Атрибуты до старта потока

Заключение

Обратная связь: goo.gl/forms/TAeraYrqJcil7GDt1

и на портале Техносферы

В следующий раз:

IPC. Pipe, FIFO. XSI и POSIX. Сокеты: доменные, обычные.

Практикум [1]

Файловая система

Есть интерфейс вида open/close/read/write. Нужно поверх него реализовать файловую систему в оперативной памяти.

 

Без директорий, по готовому шаблону, формата похожего на FAT.

Цена: 15 - 25 баллов.

Срок: 2 недели.

Положить на ваш гитхаб и сказать мне его. Сдавать как угодно - лично/удаленно.

Практикум [2]

enum ufs_error_code
ufs_errno();

int
ufs_open(const char *filename, int flags);

ssize_t
ufs_write(int fd, const char *buf, size_t size);

ssize_t
ufs_read(int fd, char *buf, size_t size);

int
ufs_close(int fd);

int
ufs_delete(const char *filename);

Интерфейс для реализации

Практикум [3]

struct block {
	/** Block memory. */
	char *memory;
	/** How many bytes are occupied. */
	int occupied;
	/** Next block in the file. */
	struct block *next;
	/** Previous block in the file. */
	struct block *prev;

	/* PUT HERE OTHER MEMBERS */
};

struct file {
	/** Double-linked list of file blocks. */
	struct block *block_list;
	/**
	 * Last block in the list above for fast access to the end
	 * of file.
	 */
	struct block *last_block;
	/** How many file descriptors are opened on the file. */
	int refs;
	/** File name. */
	const char *name;
	/** Files are stored in a double-linked list. */
	struct file *next;
	struct file *prev;

	/* PUT HERE OTHER MEMBERS */
};

Структуры, описывающие файл

Системное программирование 6

By Vladislav Shpilevoy

Системное программирование 6

Потоки в linux, представление в ядре. Отличие от процессов. POSIX. Синхронизация: mutex, rw-lock, condition variable, spin lock, barrier. Атомарные операции. Атрибуты потоков и объектов синхронизации.

  • 1,899