потоки в модуле ядра

Вопросы программного кода и архитектуры Linux

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 12:53

Это составная часть разборок модули ядра (римэйк)...

Тут есть 2 важных момента:

1. То, что работало с ядрами 2.6.38-3.15 с потоками в ядре - даже не компилируется в новых ядрах!
Даже их основной, на то время, способ создания нового потока.

2. Это малоизвестно (многим неизвестно :lol: ), что:
- в ядре Linux можно создавать/запускать собственные параллельные потоки выполнения...
- что параллельные ветки выполнения в ядре есть далеко не в каждой операционной системе...
- что потоки ядра и потоки пользовательского пространства ничем не отличаются, и заносятся в ядре в единый связный кольцевой список... в котором единообразно и диспетчируются...
- что с точки зрения ядра как-раз поток является деятельной единицей в системе, а вовсе не процесс, как может показаться внешнему наблюдателю за командным интерпретатором bash...

И вообще - разбирательство с потоками ядра даёт очень много в понимании как работает система Linux.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 12:57

Olej писал(а):
15 авг 2022, 12:53
даже не компилируется в новых ядрах!
Пример, который приводится во многих учебниках "про Linux":

Код: Выделить всё

#include <linux/module.h> 
#include <linux/sched.h> 
#include <linux/delay.h> 

static int param = 3;
module_param(param, int, 0);

static int thread(void * data) { 
   printk(KERN_INFO "thread: child process [%d] is running\n", current->pid);
   ssleep(param);                                   /* Пауза 3 с. или как параметр укажет... */ 
   printk(KERN_INFO "thread: child process [%d] is completed\n", current->pid);
   return 0; 
} 

static int test_thread(void) {
   pid_t pid; 
   printk(KERN_INFO "thread: main process [%d] is running\n", current->pid);
   pid = kernel_thread(thread, NULL, CLONE_FS);     /* Запускаем новый поток */ 
   ssleep(5);                                       /* Пауза 5 с. */ 
   printk(KERN_INFO "thread: main process [%d] is completed\n", current->pid);
   return -1; 
} 

module_init(test_thread);
MODULE_LICENSE("GPL");
Это работало ... году в 2012, а теперь толком даже не компилируется!
Вложения
mod_thr1.c
(911 байт) 31 скачивание

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 13:07

Olej писал(а):
15 авг 2022, 12:57
Это работало ... году в 2012, а теперь толком даже не компилируется!
Вот как "загадочно" это (компиляция) происходит в ядре 4.9 - это дистрибутив antiX 21 (последний):

Код: Выделить всё

$ uname -r
4.9.0-279-antix.1-486-smp

Код: Выделить всё

$ make
make -C /lib/modules/4.9.0-279-antix.1-486-smp/build M=/home/olej/2022/kernel/thread/tcreat modules
make[1]: вход в каталог «/usr/src/linux-headers-4.9.0-279-antix.1-486-smp»
  CC [M]  /home/olej/2022/kernel/thread/tcreat/mod_thr1.o
  Building modules, stage 2.
  MODPOST 1 modules
WARNING: "kernel_thread" [/home/olej/2022/kernel/thread/tcreat/mod_thr1.ko] undefined!
  CC      /home/olej/2022/kernel/thread/tcreat/mod_thr1.mod.o
  LD [M]  /home/olej/2022/kernel/thread/tcreat/mod_thr1.ko
make[1]: выход из каталога «/usr/src/linux-headers-4.9.0-279-antix.1-486-smp»
Обращаем внимание! Оно компилируется и собирается!
Не выдетает с синтаксическими ошибками (как следовало бы), а собирается с какими то там WARNING...

Код: Выделить всё

$ ls -l *.ko
-rw-r--r-- 1 olej olej 3760 авг 14 13:27 mod_thr1.ko

Код: Выделить всё

$ modinfo mod_thr1.ko
filename:       /home/olej/2022/kernel/thread/tcreat/mod_thr1.ko
license:        GPL
depends:
retpoline:      Y
vermagic:       4.9.0-279-antix.1-486-smp SMP mod_unload modversions 486
parm:           param:int
Есть! Собралось...

Код: Выделить всё

$ sudo insmod mod_thr1.ko
[sudo] пароль для olej:
insmod: ERROR: could not insert module mod_thr1.ko: Unknown symbol in module
Всё! Гоплык... :cry:

Код: Выделить всё

$ dmesg | tail -n1
[12225.904334] mod_thr1: Unknown symbol kernel_thread (err 0)
Но! :!: :!: :!:

Код: Выделить всё

$ sudo grep kernel_thread /proc/kallsyms
c9058790 T kernel_thread
c97ffe21 T thaw_kernel_threads
c97ffec3 T freeze_kernel_threads

$ sudo grep ' kernel_thread' /proc/kallsyms
c9058790 T kernel_thread
Символ kernel_thread есть и даже среди экспортируемых символов ядра.
За 15 лет работы с драйверами и модулями ядра я такого не видел ... по крайней мере, не припомню :-(

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 13:29

Olej писал(а):
15 авг 2022, 13:07
Вот как "загадочно" это (компиляция) происходит в ядре 4.9 - это дистрибутив antiX 21 (последний):
Ядро/система: 5.4 , Mint 20.3:

Код: Выделить всё

$ inxi -S
System:    Host: R420 Kernel: 5.4.0-124-generic x86_64 bits: 64 Desktop: Cinnamon 5.2.7 Distro: Linux Mint 20.3 Una 
Имя среди имён ядра:

Код: Выделить всё

olej@R420:~/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat$ sudo grep ' kernel_thread' /proc/kallsyms
ffffffff9bc98df0 T kernel_thread
В интерактивном справочнике кодов ядра BootLin https://elixir.bootlin.com/linux/v5.4/s ... task.h#L96

Код: Выделить всё

...
extern pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);
...
Поехали...

Код: Выделить всё

olej@R420:~/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat$ make
make -C /lib/modules/5.4.0-124-generic/build M=/home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat modules
make[1]: вход в каталог «/usr/src/linux-headers-5.4.0-124-generic»
  CC [M]  /home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat/mod_thr1.o
/home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat/mod_thr1.c: In function ‘test_thread’:
/home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat/mod_thr1.c:18:10: error: implicit declaration of function ‘kernel_thread’; did you mean ‘test_thread’? [-Werror=implicit-function-declaration]
   18 |    pid = kernel_thread(thread, NULL, CLONE_FS);     /* Запускаем новый поток */
      |          ^~~~~~~~~~~~~
      |          test_thread
cc1: some warnings being treated as errors
make[2]: *** [scripts/Makefile.build:270: /home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat/mod_thr1.o] Ошибка 1
make[1]: *** [Makefile:1762: /home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat] Ошибка 2
make[1]: выход из каталога «/usr/src/linux-headers-5.4.0-124-generic»
make: *** [Makefile:13: default] Ошибка 2
Всё ещё хуже :evil:
Добавил в исходник:

Код: Выделить всё

#include <linux/sched/task.h>
Теперь:

Код: Выделить всё

olej@R420:~/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat$ make
make -C /lib/modules/5.4.0-124-generic/build M=/home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat modules
make[1]: вход в каталог «/usr/src/linux-headers-5.4.0-124-generic»
  CC [M]  /home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat/mod_thr1.o
  Building modules, stage 2.
  MODPOST 1 modules
ERROR: "kernel_thread" [/home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat/mod_thr1.ko] undefined!
make[2]: *** [scripts/Makefile.modpost:94: __modpost] Ошибка 1
make[1]: *** [Makefile:1675: modules] Ошибка 2
make[1]: выход из каталога «/usr/src/linux-headers-5.4.0-124-generic»
make: *** [Makefile:13: default] Ошибка 2
Теперь WARNING перешёл в ERROR и модуль вообще не собирается!

Код: Выделить всё

olej@R420:~/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat$ ls *.ko
ls: невозможно получить доступ к '*.ko': Нет такого файла или каталога
P.S. С этим нужно будет обязательно разобраться!

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 15:02

Olej писал(а):
15 авг 2022, 12:57
Это работало ... году в 2012, а теперь толком даже не компилируется!
А теперь к тому что работает: "новый интерфейс" потоков ядра: kthread_create(), kthread_run() и др.
Небольшой файл prefix.c - для индикации (вывода), чтобы каждый раз его не включать в код:

Код: Выделить всё

static char *sj(void) {    // метка времени
   static char s[40];
   sprintf(s, "%08ld : ", jiffies);
   return s;
}

static char *st(int lvl) { // метка потока
   static char s[40];
   sprintf(s, "%skthread [%05d:%d]", sj(), current->pid, lvl);
   return s;
}
И сам код:

Код: Выделить всё

#include <linux/module.h> 
#include <linux/delay.h> 
#include <linux/jiffies.h> 
#include <linux/kthread.h>

#include "../prefix.c"

static int N = 2;            // N - число потоков
module_param(N, uint, 0);

static int thread_fun1(void* data) {
   uintptr_t N = *(uint*)data;
   struct task_struct *t1 = NULL;
   printk("%s is parent [%05d]\n", st(N), current->parent->pid);
   if(--N > 0)
      t1 = kthread_run(thread_fun1, (void*)&N, "my_thread_%ld", N);
   while(!kthread_should_stop()) {
      // выполняемая работа потоковой функции 
      msleep(100);
   }
   printk("%s find signal!\n", st(N));
   if(t1 != NULL) kthread_stop(t1);   
   printk("%s is completed\n", st(N));
   return 0;
}

static int test_thread(void) {
   struct task_struct *t1;
   printk("%smain process [%d] is running\n", sj(), current->pid);
   t1 = kthread_run(thread_fun1, (void*)&N, "my_thread_%d", N);
   msleep(10000);
   kthread_stop(t1);   
   printk("%smain process [%d] is completed\n", sj(), current->pid);
   return -1; 
} 

module_init(test_thread);
MODULE_LICENSE("GPL");
Вложения
mod_thr3.c
(1.09 КБ) 31 скачивание

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 15:08

Olej писал(а):
15 авг 2022, 15:02
И сам код:
И как это работает:
Makefile:

Код: Выделить всё

CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)

#obj-m = mod_thr1.o 
#obj-m = mod_thr2.o 
obj-m += mod_thr3.o
#obj-m += mod_for.o 

all: default clean

default:
        $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
        @rm -f *.o .*.cmd .*.flags *.mod.c *.order *.mod
        @rm -f .*.*.cmd *.symvers *~ *.*~ TODO.* .*.d
        @rm -fR .tmp*
        @rm -rf .tmp_versions

disclean: clean
        @rm -f *.ko

Код: Выделить всё

olej@R420:~/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat$ make
make -C /lib/modules/5.4.0-124-generic/build M=/home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat modules
make[1]: вход в каталог «/usr/src/linux-headers-5.4.0-124-generic»
  CC [M]  /home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat/mod_thr3.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC [M]  /home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat/mod_thr3.mod.o
  LD [M]  /home/olej/2022/own.BOOKs/BHV.kernel/examples/thread/tcreat/mod_thr3.ko
make[1]: выход из каталога «/usr/src/linux-headers-5.4.0-124-generic»
Выполнение:

Код: Выделить всё

$ time sudo insmod mod_thr3.ko N=3
insmod: ERROR: could not insert module mod_thr3.ko: Operation not permitted
real    0m13,246s
user    0m0,117s
sys     0m0,030s
Во время паузы выполнения этой команды мы можем в другом терминале посмотреть состояние потоков ядра в динамике:

Код: Выделить всё

$ ps -ef | grep '\[' | grep 'my_'
root     10037     2  0 00:17 ?        00:00:00 [my_thread_3]
root     10038     2  0 00:17 ?        00:00:00 [my_thread_2]
root     10039     2  0 00:17 ?        00:00:00 [my_thread_1]
И итоговый вывод (в журнал) по итогам выполнения приложения:

Код: Выделить всё

$ dmesg | tail -n14
[ 3039.003701] mod_thr3: loading out-of-tree module taints kernel.
[ 3039.003708] mod_thr3: module license 'GPL2' taints kernel.
[ 3039.003710] Disabling lock debugging due to kernel taint
[ 3039.003921] 4297706215 : main process [10036] is running
[ 3039.004023] 4297706215 : kthread [10037:3] is parent [00002]
[ 3039.004290] 4297706215 : kthread [10038:2] is parent [00002]
[ 3039.004376] 4297706216 : kthread [10039:1] is parent [00002]
[ 3049.092651] 4297716304 : kthread [10037:2] find signal!
[ 3049.092659] 4297716304 : kthread [10038:1] find signal!
[ 3049.196648] 4297716408 : kthread [10039:0] find signal!
[ 3049.196651] 4297716408 : kthread [10039:0] is completed
[ 3049.196676] 4297716408 : kthread [10038:1] is completed
[ 3049.196734] 4297716408 : kthread [10037:2] is completed
[ 3049.196752] 4297716408 : main process [10036] is completed
Здесь хорошо видно, что:
• порядок, в котором потоки создаются, и порядок, в котором они получают сигнал на завершение — совпадают...
• но порядок фактического завершения — в точности обратный, потому, что на выполнении kthread_stop() поток блокируется и ожидает завершения дочернего потока, и только после этого имеет право завершиться;

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 15:16

Olej писал(а):
15 авг 2022, 15:02
И сам код:
В этом коде для синхронизации завершения использовался механизм kthread_stop() + kthread_should_stop().
А теперь - с использованием complete() + wait_for_completion:

Код: Выделить всё

#include <linux/module.h> 
#include <linux/delay.h> 
#include <linux/kthread.h>
#include <linux/jiffies.h> 
#include <linux/semaphore.h>

#include "../prefix.c"

#define NUM 3
static struct completion finished[NUM];

#define DELAY 1000
static int thread_func(void* data) { 
   uint num = *(uint*)data;
   printk("! %s is running\n", st(num));
   msleep(DELAY - num * 200); 
   complete(finished + num );
   printk("! %s is finished\n", st(num));
   return 0;
}

#define IDENT "for_thread_%d"
static int test_mlock(void) { 
   struct task_struct *t[NUM];
   uint i;
   for(i = 0; i < NUM; i++)
      init_completion(finished + i);
   for(i = 0; i < NUM; i++)
      t[i] = kthread_run(thread_func, (void*)&i, IDENT, i);
   for(i = 0; i < NUM; i++)
      wait_for_completion(finished + i);
   printk("! %s is finished\n", st(NUM));
   return -1; 
} 

module_init(test_mlock);
MODULE_LICENSE("GPL");
Собираем всё тем же Makefile...

Код: Выделить всё

$ time sudo insmod mod_for.ko
insmod: ERROR: could not insert module mod_for.ko: Operation not permitted
real    0m1,077s
user    0m0,009s
sys     0m0,021s

Код: Выделить всё

$ dmesg | tail -n6
[33242.368422] ! 32942846 : kthread [17213:2] is running
[33242.368427] ! 32942846 : kthread [17214:0] is running
[33243.010874] ! 32943488 : kthread [17213:2] is finished
[33243.202412] ! 32943680 : kthread [17212:1] is finished
[33243.393998] ! 32943872 : kthread [17214:0] is finished
[33243.394007] ! 32943872 : kthread [17211:3] is finished
Вложения
mod_for.c
(897 байт) 31 скачивание

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 15:29

Olej писал(а):
15 авг 2022, 15:16
для синхронизации завершения использовался механизм
К вопросу синхронизации вообще ... и механизмам синхронизации в частности...
Синхронизация на мютексах ядра:

Код: Выделить всё

#include <linux/module.h> 
#include <linux/delay.h> 
#include <linux/kthread.h>
#include <linux/mutex.h> 

#include "../prefix.c"

#define num 4                    // num - число рабочих потоков

static ulong rep = 10000;        // rep - число повторений (объём работы)
module_param(rep, ulong, 0);

static bool sync = 1;            // sync - синхронизация: 0 - да, 1 - нет
module_param(sync, bool, 0);

DEFINE_MUTEX(mtx); 
static ulong locked = 0;         // накапливаемая сумма

static struct completion finished[num];

static int thread_func(void* data) { 
   uint i, n = *(uint*)data;     // порядковый номер потока (локальный!)
   printk("! %s is started\n", st(n));
   for(i = 0; i < rep; i++) {
      if(!sync) mutex_lock(&mtx);
      locked++;   
      if(!sync) mutex_unlock(&mtx);
   }
   printk("! %s is finished\n", st(n));
   complete(finished + n);
   return 0;
}

static int test_mlock(void) {
   unsigned long j1 = jiffies;   // время точки старта
   struct task_struct *t[num];
   uint i;
   printk("! потоков = %d | повторов = %ld | синхронизация: %s\n",
          num, rep, sync ? "нет" : "да" );
   for(i = 0; i < num; i++)
      init_completion(finished + i);
   for(i = 0; i < num; i++)
      #define IDENT "mlock_thread_%d"
      t[i] = kthread_run(thread_func, (void*)&i, IDENT, i);
   for(i = 0; i < num; i++)
      wait_for_completion(finished + i);
   j1 = jiffies - j1;
   printk("! время работы: %ld.%02ld сек. (%ld), сумма=%ld\n",
           j1 / HZ, (j1 * 100) / HZ % 100, j1, locked);
   return -1; 
} 

module_init(test_mlock);
MODULE_LICENSE("GPL");
Логика кода проста: в 4 параллельных потока (фиксированное число для простоты) с идентичной функцией потока мы инкрементируем целочисленную переменную locked в большом числе циклов повторения. Причём, в качестве критической секции кода мы сознательно выбираем единичную операцию инкремента, потому что, иногда, в публикациях удаётся прочитать что инкремент — это атомарная операция. Но выполнять критическую секцию кода мы можем блокированием её мютексом mtx, или нет, в зависимости от опции загрузки модуля sync (0 — да, 1 — нет, по умолчанию — да).

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 15:31

Olej писал(а):
15 авг 2022, 15:29
Логика кода проста:
Наблюдаем:

Код: Выделить всё

$ time sudo insmod mlock.ko sync=0 rep=10000000
insmod: ERROR: could not insert module mlock.ko: Operation not permitted
real	0m10,166s
user	0m0,042s
sys	0m0,013s

Код: Выделить всё

$ dmesg | tail -n10
[ 4026.438466] ! потоков = 4 | повторов = 10000000 | синхронизация: да
[ 4026.438570] ! 4298693807 : kthread [19517:1] is started
[ 4026.438790] ! 4298693807 : kthread [19518:2] is started
[ 4026.443870] ! 4298693813 : kthread [19519:3] is started
[ 4026.450833] ! 4298693820 : kthread [19520:0] is started
[ 4036.071615] ! 4298703440 : kthread [19520:0] is finished
[ 4036.250586] ! 4298703619 : kthread [19519:3] is finished
[ 4036.527005] ! 4298703896 : kthread [19517:1] is finished
[ 4036.539002] ! 4298703908 : kthread [19518:2] is finished
[ 4036.539019] ! время работы: 10.10 сек. (10101), сумма=40000000
Это было выполнение с синхронизацией: как и ожидалось 4*10000000 инкрементов создали значение 40000000. А теперь, раз всё так хорошо, и понадеявшись на «авось» мы уберём синхронизацию:

Код: Выделить всё

$ time sudo insmod mlock.ko sync=1 rep=10000000
insmod: ERROR: could not insert module mlock.ko: Operation not permitted
real	0m0,597s
user	0m0,042s
sys	0m0,013s
Теперь время выполнения уменьшилось почти в 20 раз! Это большая победа оптимизации … Но:

Код: Выделить всё

$ dmesg | tail -n10
[ 4009.641894] ! потоков = 4 | повторов = 10000000 | синхронизация: нет
[ 4009.642139] ! 4298677010 : kthread [19462:1] is started
[ 4009.642161] ! 4298677011 : kthread [19463:2] is started
[ 4009.651142] ! 4298677020 : kthread [19464:3] is started
[ 4009.658137] ! 4298677027 : kthread [19465:0] is started
[ 4010.144128] ! 4298677513 : kthread [19463:2] is finished
[ 4010.168708] ! 4298677537 : kthread [19465:0] is finished
[ 4010.172651] ! 4298677541 : kthread [19462:1] is finished
[ 4010.172889] ! 4298677541 : kthread [19464:3] is finished
[ 4010.172908] ! время работы: 0.53 сек. (531), сумма=20370339
… но из ожидаемых 40000000 выполнились только 20370339, то есть около 50%. Это и есть наблюдаемые эффекты синхронизации (или её отсутствия):
1. Любой параллельный доступ к данным, который может хоть каким-то образом модифицировать эти денные, без синхронизации порождает грубейшие ошибки.
2. Синхронизации — дорогие операции (в смысле производительности)! Правильное использование синхронизации может увеличивать время выполнения на порядок, или даже на порядки… И здесь есть простор для оптимизации.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: потоки в модуле ядра

Непрочитанное сообщение Olej » 15 авг 2022, 15:47

Olej писал(а):
15 авг 2022, 15:29
Синхронизация на мютексах ядра:
И в завершение - синхронизация семафорами ядра + заодно наблюдение уровней вложенности синхронизаций (критических секций):

Код: Выделить всё

#include <linux/module.h> 
#include <linux/delay.h> 
#include <linux/kthread.h>
#include <linux/semaphore.h>

#include "../prefix.c"

static int num = 2;                // num - число рабочих потоков
module_param(num, int, 0);
static int rep = 100;              // rep - число повторений (объём работы)
module_param(rep, int, 0);
static int sync = -1;              // sync - уровень на котором синхронизация
module_param(sync, int, 0);
static int max_level = 2;          // max_level - уровень глубины вложенности
module_param(max_level, int, 0);

DEFINE_SEMAPHORE(sema);

static long locked = 0;

static long loop_func(int lvl){ 
   long n = 0;
   if(lvl == sync){ down(&sema); locked++; }
   if(0 == lvl) {
      const int tick = 1;
      msleep(tick);                // выполняемая работа потока 
      n = 1;
   }
   else {
      int i;
      for(i = 0; i < rep; i++) {
         n += loop_func(lvl - 1);
      }
   }
   if(lvl == sync) up(&sema);
   return n;
}

struct param {                     // параметр потока
   int    num;
   struct completion finished;
};

#define IDENT "mlock_thread_%d"
static int thread_func(void* data) { 
   long n = 0;
   struct param *parent = (struct param*)data;
   int num = parent->num - 1;      // порядковый номер потока (локальный!)
   struct task_struct *t1 = NULL;
   struct param parm;
   printk("! %s is running\n", st(num));
   if(num > 0) {
      init_completion(&parm.finished);
      parm.num = num;
      t1 = kthread_run(thread_func, (void*)&parm, IDENT, num);
   }
   n = loop_func(max_level);       // рекурсивный вызов вложенных циклов
   if(t1 != NULL)
      wait_for_completion(&parm.finished);
   complete(&parent->finished);
   printk("! %s do %ld units\n", st(num), n);
   return 0;
}

static int test_mlock(void) {
   struct task_struct *t1;
   struct param parm;
   unsigned j1 = jiffies, j2;
   if(sync > max_level) sync = -1; // без синхронизации
   printk("! repeat %d times in %d levels; synch. in level %d\n",
           rep, max_level, sync);
   init_completion(&parm.finished);
   parm.num = num;
   t1 = kthread_run(thread_func, (void*)&parm, IDENT, num);
   wait_for_completion(&parm.finished);
   printk("! %s is finished\n", st(num));
   j2 = jiffies - j1;
   printk("! working time was %d.%1d seconds, locked %ld times\n",
           j2 / HZ, (j2 * 10) / HZ % 10, locked);
   return -1; 
} 

module_init(test_mlock);
MODULE_LICENSE("GPL");
Модуль достаточно сложный (не по коду, а по логике), поэтому некоторые краткие комментарии:
• включаемый файл определяет функцию st() для форматирования диагностики о потоке (с меткой времени jiffies), эту функцию мы уже видели ранее в обсуждении создания потоков;
• потоки (числом num — параметр запуска модуля) запускают друг друга последовательно, и завершаются в обратном порядке: каждый поток ожидает завершения им порождённого (по типу как это делается в рекурсии, но только в динамике … здесь нет как таковой рекурсии);
• «работа» потока состоит в циклическом (параметр: rep раз) выполнении рекурсивной функции loop_func();

Код: Выделить всё

for(j1; ...)
• рекурсия, вообще то говоря, крайне рискованная вещь в модулях ядра, из-за ограниченности и фиксированного размера стека ядра, но в данном случае а). это иллюстрационная задача (и она, попутно, показывает возможность рекурсии в коде ядра), б). функция сознательно имеет минимальной число локальных (стековых) переменных, в). причина, которая весит больше всех остальных вместе взятых — рекурсия позволяет создать структуру вложенных циклов переменной и произвольно большой глубины вложенности (параметр max_level модуля), вызов loop_func(N) эквивалентен:

Код: Выделить всё

        for(j2; ...)
           for(j3; ...)
           ...
              for(jN; ...) 
• варьируя параметр модуля sync, можно заказывать, на какой глубине вложенных циклов потоки станут пытаться синхронизироваться захватом семафора sema: sync=0 — на самом глубоком уровне имитации «работы» потока, sync=1 — уровнем выше, ... sync=max_level — на максимально возможном верхнем уровне охватывающего цикла, sync<0 или sync>max_level — вообще не синхронизироваться, не пытаться получить доступ к семафору sema;
• модуль выполнен в уже полюбившейся нам манере исполнения: как пользовательская задача, ничего не устанавливающая в ядре, но выполняющаяся в режиме защиты супервизора.

Ответить

Вернуться в «Linux изнутри»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 6 гостей