Форум программистов, компьютерный форум, киберфорум
C/С++ под Linux
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.85/13: Рейтинг темы: голосов - 13, средняя оценка - 4.85
 Аватар для peter_irich
367 / 223 / 53
Регистрация: 18.10.2017
Сообщений: 2,387

Драйвер для ядра 3.x и старше

12.06.2018, 23:06. Показов 2845. Ответов 29
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте!

Есть где-нибудь руководство по написанию драйверов для Linux с ядром 3.x и 4.x?
Конкретно надо для ядра 3.16, связанный с константами для клавиатуры.
В файле include/uapi/linux/input.h нет их описаний, также не вполне понятно, как создавать свои ioctl().
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
12.06.2018, 23:06
Ответы с готовыми решениями:

Как установить драйвер ядра для VirtuaBox?
доброго времени суток ! хочу установить Virtual Box на Ubuntu 13.04 64bit (недавно сама обновилась =) ) но при запуске машины выдает...

Драйвер службы ядра
Пишу программку. Скинул ее потестить человеку на другую машину- и вылазит постоянное предупреждение:

Драйвер ядра устройства NULL
В стандартной утилите "Сведения о системе", пункт "Устройство с неполадками" присутствует пункт: "устройство - NULL, код устройства...

29
2 / 2 / 0
Регистрация: 19.06.2018
Сообщений: 41
04.07.2018, 00:30
Студворк — интернет-сервис помощи студентам
1) Смотрим адрес функции input_event в модуле
2) Смотрим какой функции, соответствует данный адрес в /proc/kallsyms
3) Смотрим код функции, из пункта 2 в исходниках ядра. И понимаем, что там вызывается или не вызывается.



https://elixir.bootlin.com/lin... linux/fs.h
в struct file_operations полно всяких функций. Может стоит попробовать с ними поиграться?
0
 Аватар для peter_irich
367 / 223 / 53
Регистрация: 18.10.2017
Сообщений: 2,387
18.07.2018, 12:31  [ТС]
sgaeal, Я воспользовался вашим советом, посмотрел, что делается в input_event() и вызываемых
из неё и далее функциях, вызвал их вручную из своего модуля со своими данными и понял, в чём ошибка.
Вот исправленный вариант собственно модуля:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#include <linux/slab.h>
#include "dev.h"
#include "ioctl.h"
 
#define MYEVENT_MAJOR 201
#define MYEVENT_MODNAME "my_event"
 
//relative /sys: /devices/virtual/input/inputNN
 
// Работа с символьным устройством в старом стиле...
static int dev_open( struct inode *n, struct file *f ) {
   // ... при этом MINOR номер устройства должна обслуживать функция open:
   // unsigned int minor = iminor( n );
   return 0;
}
 
static int dev_release( struct inode *n, struct file *f ) {
   return 0;
}
 
static struct input_dev *i_dev;
//static char dname[16];
 
static long dev_ioctl( struct file *f,
                      unsigned int cmd, unsigned long arg ) {
int ni, nr, nc, nv;
  nr = 0;
   if( ( _IOC_TYPE( cmd ) != IOC_MAGIC ) ) return -ENOTTY;
   switch( cmd ) {
//  case MYEVENT_GET_STRING:
//        if( copy_to_user( (void *)arg, hello_str, _IOC_SIZE( cmd ) ) ) nr =  -EFAULT;
//    break;
//    case MYEVENT_CMD_TO:
//    ncmd1 = 123;
//    if( copy_to_user( (void *)arg, (void *)&ncmd1, _IOC_SIZE( cmd ) ) ) nr =  -EFAULT;
//    break;
    case MYEVENT_CMD_KEY:
      ni = copy_from_user( (void *)&data.n, (void *)arg, _IOC_SIZE( cmd ) );
      if(ni != 0){
        nr =  -EFAULT;
        printk(KERN_ERR "my_event from %d\n", ni);
      }
//  printk(KERN_INFO "my_event from %d\n", data.n);
      nc = data.n;
      nv = (nc > 0) ? 1 : 0;
      if(nv == 0)
        nc = -nc;
//  printk(KERN_INFO "my_event from %d %d\n", nv, nc);
      input_event(i_dev, EV_KEY, nc, nv);
      input_sync(i_dev);
      break;
    default: 
     nr =  -ENOTTY;
  }
  return nr;
}
 
static const struct file_operations event_fops = {
   .owner = THIS_MODULE,
   .open = dev_open,
   .release = dev_release,
//   .read  = dev_read,
#if(LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0))
   .ioctl = dev_ioctl
#else
   .unlocked_ioctl = dev_ioctl
#endif
};
 
int init_module(void){
int error;
int nt,nc,nv;       //--
 
  i_dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
  i_dev = input_allocate_device();
  if (!i_dev) {
    printk(KERN_ERR "my_event.c: Not enough memory\n");
    error = -ENOMEM;
    goto err_err;
  }
  i_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_MSC) | BIT_MASK(EV_MAX);
  i_dev->keybit[BIT_WORD(KEY_ESC)] |= BIT_MASK(KEY_ESC);
  i_dev->keybit[BIT_WORD(KEY_1)] |= BIT_MASK(KEY_1);
  i_dev->keybit[BIT_WORD(KEY_2)] |= BIT_MASK(KEY_2);
  i_dev->keybit[BIT_WORD(KEY_3)] |= BIT_MASK(KEY_3);
  i_dev->keybit[BIT_WORD(KEY_4)] |= BIT_MASK(KEY_4);
  i_dev->keybit[BIT_WORD(KEY_5)] |= BIT_MASK(KEY_5);
  i_dev->keybit[BIT_WORD(KEY_6)] |= BIT_MASK(KEY_6);
  i_dev->keybit[BIT_WORD(KEY_7)] |= BIT_MASK(KEY_7);
  i_dev->keybit[BIT_WORD(KEY_8)] |= BIT_MASK(KEY_8);
  i_dev->keybit[BIT_WORD(KEY_9)] |= BIT_MASK(KEY_9);
  i_dev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
  i_dev->keybit[BIT_WORD(KEY_MINUS)] |= BIT_MASK(KEY_MINUS);
  i_dev->keybit[BIT_WORD(KEY_EQUAL)] |= BIT_MASK(KEY_EQUAL);
  i_dev->keybit[BIT_WORD(KEY_BACKSPACE)] |= BIT_MASK(KEY_BACKSPACE);
  i_dev->keybit[BIT_WORD(KEY_TAB)] |= BIT_MASK(KEY_TAB);
  i_dev->keybit[BIT_WORD(KEY_Q)] |= BIT_MASK(KEY_Q);
  i_dev->keybit[BIT_WORD(KEY_W)] |= BIT_MASK(KEY_W);
  i_dev->keybit[BIT_WORD(KEY_E)] |= BIT_MASK(KEY_E);
  i_dev->keybit[BIT_WORD(KEY_R)] |= BIT_MASK(KEY_R);
  i_dev->keybit[BIT_WORD(KEY_T)] |= BIT_MASK(KEY_T);
  i_dev->keybit[BIT_WORD(KEY_Y)] |= BIT_MASK(KEY_Y);
  i_dev->keybit[BIT_WORD(KEY_U)] |= BIT_MASK(KEY_U);
  i_dev->keybit[BIT_WORD(KEY_I)] |= BIT_MASK(KEY_I);
  i_dev->keybit[BIT_WORD(KEY_O)] |= BIT_MASK(KEY_O);
  i_dev->keybit[BIT_WORD(KEY_P)] |= BIT_MASK(KEY_P);
  i_dev->keybit[BIT_WORD(KEY_LEFTBRACE)] |= BIT_MASK(KEY_LEFTBRACE);
  i_dev->keybit[BIT_WORD(KEY_RIGHTBRACE)] |= BIT_MASK(KEY_RIGHTBRACE);
  i_dev->keybit[BIT_WORD(KEY_ENTER)] |= BIT_MASK(KEY_ENTER);
  i_dev->keybit[BIT_WORD(KEY_LEFTCTRL)] |= BIT_MASK(KEY_LEFTCTRL);
  i_dev->keybit[BIT_WORD(KEY_A)] |= BIT_MASK(KEY_A);
  i_dev->keybit[BIT_WORD(KEY_S)] |= BIT_MASK(KEY_S);
  i_dev->keybit[BIT_WORD(KEY_D)] |= BIT_MASK(KEY_D);
  i_dev->keybit[BIT_WORD(KEY_F)] |= BIT_MASK(KEY_F);
  i_dev->keybit[BIT_WORD(KEY_G)] |= BIT_MASK(KEY_G);
  i_dev->keybit[BIT_WORD(KEY_H)] |= BIT_MASK(KEY_H);
  i_dev->keybit[BIT_WORD(KEY_J)] |= BIT_MASK(KEY_J);
  i_dev->keybit[BIT_WORD(KEY_K)] |= BIT_MASK(KEY_K);
  i_dev->keybit[BIT_WORD(KEY_L)] |= BIT_MASK(KEY_L);
  i_dev->keybit[BIT_WORD(KEY_SEMICOLON)] |= BIT_MASK(KEY_SEMICOLON);
  i_dev->keybit[BIT_WORD(KEY_APOSTROPHE)] |= BIT_MASK(KEY_APOSTROPHE);
  i_dev->keybit[BIT_WORD(KEY_GRAVE)] |= BIT_MASK(KEY_GRAVE);
  i_dev->keybit[BIT_WORD(KEY_LEFTSHIFT)] |= BIT_MASK(KEY_LEFTSHIFT);
  i_dev->keybit[BIT_WORD(KEY_BACKSLASH)] |= BIT_MASK(KEY_BACKSLASH);
  i_dev->keybit[BIT_WORD(KEY_Z)] |= BIT_MASK(KEY_Z);
  i_dev->keybit[BIT_WORD(KEY_X)] |= BIT_MASK(KEY_X);
  i_dev->keybit[BIT_WORD(KEY_C)] |= BIT_MASK(KEY_C);
  i_dev->keybit[BIT_WORD(KEY_V)] |= BIT_MASK(KEY_V);
  i_dev->keybit[BIT_WORD(KEY_B)] |= BIT_MASK(KEY_B);
  i_dev->keybit[BIT_WORD(KEY_N)] |= BIT_MASK(KEY_N);
  i_dev->keybit[BIT_WORD(KEY_M)] |= BIT_MASK(KEY_M);
  i_dev->keybit[BIT_WORD(KEY_COMMA)] |= BIT_MASK(KEY_COMMA);
  i_dev->keybit[BIT_WORD(KEY_DOT)] |= BIT_MASK(KEY_DOT);
  i_dev->keybit[BIT_WORD(KEY_SLASH)] |= BIT_MASK(KEY_SLASH);
  i_dev->keybit[BIT_WORD(KEY_RIGHTSHIFT)] |= BIT_MASK(KEY_RIGHTSHIFT);
  i_dev->keybit[BIT_WORD(KEY_LEFTALT)] |= BIT_MASK(KEY_LEFTALT);
  i_dev->keybit[BIT_WORD(KEY_SPACE)] |= BIT_MASK(KEY_SPACE);
  i_dev->keybit[BIT_WORD(KEY_CAPSLOCK)] |= BIT_MASK(KEY_CAPSLOCK);
  i_dev->keybit[BIT_WORD(KEY_F1)] |= BIT_MASK(KEY_F1);
  i_dev->keybit[BIT_WORD(KEY_F2)] |= BIT_MASK(KEY_F2);
  i_dev->keybit[BIT_WORD(KEY_F3)] |= BIT_MASK(KEY_F3);
  i_dev->keybit[BIT_WORD(KEY_F4)] |= BIT_MASK(KEY_F4);
  i_dev->keybit[BIT_WORD(KEY_F5)] |= BIT_MASK(KEY_F5);
  i_dev->keybit[BIT_WORD(KEY_F6)] |= BIT_MASK(KEY_F6);
  i_dev->keybit[BIT_WORD(KEY_F7)] |= BIT_MASK(KEY_F7);
  i_dev->keybit[BIT_WORD(KEY_F8)] |= BIT_MASK(KEY_F8);
  i_dev->keybit[BIT_WORD(KEY_F9)] |= BIT_MASK(KEY_F9);
  i_dev->keybit[BIT_WORD(KEY_F10)] |= BIT_MASK(KEY_F10);
  i_dev->keybit[BIT_WORD(KEY_F11)] |= BIT_MASK(KEY_F11);
  i_dev->keybit[BIT_WORD(KEY_F12)] |= BIT_MASK(KEY_F12);
  i_dev->keybit[BIT_WORD(KEY_UP)] |= BIT_MASK(KEY_UP);
  i_dev->keybit[BIT_WORD(KEY_PAGEUP)] |= BIT_MASK(KEY_PAGEUP);
  i_dev->keybit[BIT_WORD(KEY_LEFT)] |= BIT_MASK(KEY_LEFT);
  i_dev->keybit[BIT_WORD(KEY_RIGHT)] |= BIT_MASK(KEY_RIGHT);
  i_dev->keybit[BIT_WORD(KEY_DOWN)] |= BIT_MASK(KEY_DOWN);
  i_dev->keybit[BIT_WORD(KEY_PAGEDOWN)] |= BIT_MASK(KEY_PAGEDOWN);
  i_dev->keybit[BIT_WORD(KEY_MAX)] |= BIT_MASK(KEY_MAX);
 
  strcpy(name, "my_event");
  i_dev->name = name;
  i_dev->id.bustype = BUS_I8042;
  i_dev->id.vendor = 1;
  i_dev->id.product = 1;
  i_dev->id.version = 1;
 
  error = input_register_device(i_dev);
  if (error) {
    printk(KERN_ERR "my_event.c: Failed to register device. %d\n", error);
    input_free_device(i_dev);
    goto err_err;
  }
   error = register_chrdev( MYEVENT_MAJOR, MYEVENT_MODNAME, &event_fops );
    if( error < 0 ) {
      printk( KERN_ERR "my_event_dev: can't register char device. %d\n", error );
      input_free_device(i_dev);
      goto err_err;
    } else
      printk( KERN_INFO "my_event_dev loaded.\n" );
 
    return 0;
 
//err_free_dev:
//  input_free_device(i_dev);
err_err:
    return error;
}
 
void cleanup_module( void ) {
  input_unregister_device(i_dev);
  input_free_device(i_dev);
  unregister_chrdev( MYEVENT_MAJOR, MYEVENT_MODNAME );
  printk( KERN_INFO "my_event_dev removed.\n" );
}
0
 Аватар для peter_irich
367 / 223 / 53
Регистрация: 18.10.2017
Сообщений: 2,387
20.09.2018, 22:21  [ТС]
sgaeal, А нем ожете ли Вы или ещё кто посоветовать что-нибудь, желательно побольше объёмом,
для модулей под ядра 4.x? Я пытался сейчас поискать, но вроде бы всё не новое.
0
2 / 2 / 0
Регистрация: 19.06.2018
Сообщений: 41
21.09.2018, 04:03
peter_irich,
книга "Ядро linux"
Д. Бовет, М. Чезати

2007-го года! Врятли что-то лучше найдёте.
Да и не нужно ориентироваться на...мол ядра выше 4-х.
Для меня лично, разницы нет , какое ядро 2,0,9 или самое последнее из 4-х
Ну какие там изменения? ну при переходе с 2,х на 3,х, убрали kernel_lock. Ну и что из этого? часто ли вы это использовали? не думаю.
Ну, новые переменные в структурах введены., некторые переименованы и что? легко найдёте их через grap .
Главное понимать "в общем" как ОС работает. А то, какой она версии....должно быть ПОХ.
Пока писал, вспомнил ещё одно из отличие существенное в работе. Но поверьте. Вы когда будите кодить, даже не будите догадываться о его существовании. На сколько это не существенно при кодинге!. (при работе самой ОС существенное изменение, но при кодинге, его даже не замечаешь).
0
 Аватар для peter_irich
367 / 223 / 53
Регистрация: 18.10.2017
Сообщений: 2,387
21.09.2018, 20:25  [ТС]
sgaeal,
Однако Олег Цирюлик в своей книге версии 6.245 приводит эти различия для веток 2.x и 3.x,
и сам я не раз сталкивался, что драйвер не компилируется с новым ядром.
А сейчас я обнаружил, что два используемых нами драйвера, например, для платы MIC-3612 от advantech.com,
adv950_source_v3.42.3.tar.gz, не компилируется в Astra Linux SE 1.6 с ядром 4.15 со странными жалобами
на элементарные функции - snprintf, memcpy и подобные - что они конфликтуют с built-in функциями.
Я ещё не успел их найти, не исключено, что причина не в исходном ядре, а в изменениях в нём,
сделанных в "Русбитех".
В заголовочных файлах ядра для Astra SE 1.5 у них были ошибки, из-за которых тоже что-то не компилировалось.

Различия в ядрах обязательно знать при переносе драйвера с одного на другое, так разумеется, лучше,
если описаны в систематическом виде.
0
 Аватар для peter_irich
367 / 223 / 53
Регистрация: 18.10.2017
Сообщений: 2,387
22.09.2018, 17:18  [ТС]
Интересно, что в Ubuntu-18.04 с ядром lowlatency-4.15.0-34 этот драйвер тоже не компилируется,
но с другой ошибкой - там жалобы на несовместимость формата структуры timer_list,
а насчёт функций snprintf, memcpy и подобных жалоб нет. Как много загадочного в природе.
0
2 / 2 / 0
Регистрация: 19.06.2018
Сообщений: 41
24.09.2018, 14:34
Драйвера . Точнее модули компилируются под любое ядро Одинаково .
И пох. 2-х или 4-х ядро целевое.

Ругается на элементарные функции? а они подключены? ..точнее даже так.
При сборке к новому ядру модуля, уверены что все макросы остались одними и темеже? и что структуры одинаковые? (мол в структурах может какое макроопределение вставлено...а в новом ядре его нет). Получается ошибка. А может гдето просто макро убирает скобку..и ошибки посыпались. и дошло до memcpy.

timer_list ? это в коде объявлен список? или в исходниках ядра?..
небось в ядре. Просто некой новой структурой объявление сделали, и перестали использовать "список" который использовали для этой "переменной" ранее
.............
ну или timer_list не экспортируемая..в конце то концов.
0
 Аватар для peter_irich
367 / 223 / 53
Регистрация: 18.10.2017
Сообщений: 2,387
24.09.2018, 20:59  [ТС]
Эти, как я их назвал, элементарные функции, являются "built-in", они находятся в ядре в /lib.
Я не знаю, почему в Astra 1.6 на них жалобы, они никак не должны меняться. Во всяком случае,
в версиях 1.4 и 1.6 их описания в заголовочных файлах совпадают. Исходного кода ядра от 1.6
"Русбитех" пока не предоставляет.
struct timer_list - конечно, в ядре. Почему такие различия с ядрами 4.15 в Ubntu и в Astra - не знаю.
В Astra 1.4 драйвер с ядром 3.16, использующий timer_list, компилируется.
0
 Аватар для peter_irich
367 / 223 / 53
Регистрация: 18.10.2017
Сообщений: 2,387
25.09.2018, 18:09  [ТС]
Я ошибся, сообщение о конфликте функций snprintf и других - это только предупреждения,
а ошибки дальше. 1-я из них - о несовпадении типов параметров.
Oжидается параметр типа
const struct timespec64 *
а передаётся параметр типа
const struct timespec *
0
 Аватар для peter_irich
367 / 223 / 53
Регистрация: 18.10.2017
Сообщений: 2,387
18.03.2019, 21:11  [ТС]
У меня скомпилировался этот модуль и в Ubuntu-18.04 с ядром 4.15 после того, как я закомментировал строку
C
1
// #include <asm/uacess.h>
а тестовая программа стала выводить в желаемый xterm после того, как я научился переводить на него фокус командой
C
1
2
3
4
5
6
...
xdotool search --class "xterm" windowactivate
sleep 1
 
./ch_my_event.elf   //- тестовая программа, т.е. вывод из неё с помощью ioctl() перенаправляется в xterm.
...
А как узнать идентификатор окна из стека, о котором говорит xdotool?
Я пока не нашёл.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
18.03.2019, 21:11
Помогаю со студенческими работами здесь

Модуль ядра и драйвер устройства
Здравствуйте, чем отличается модуль ядра от драйвера устройства? Само понятие. Если я правильно понимаю то модуль ядра это более обширное...

Возможно ли приостановить/заморозить драйвер на уровне ядра
Есть драйвер windows который мне нужно заморозить на 1 минуту. Он работает на уровне нулевого кольца (ring0) В PC Hunter он загружается...

Выборка из БД мужчин от 60 и старше, женщин от 55 и старше на Foxpro 2.6
Помогите осуществить Выборку из БД мужчин от 60 и старше, женщин от 55 и старше Часть кода, где DATW - дата выписки, DATP-дата...

Выборка SQL Foxpro муж от 60 лет старше и жен от 55 лет и старше
Помогите сделать запрос sql муж от 60 лет старше и жен от 55 лет и старше одним запросом Добавлено через 5 минут SELECT * FROM...

Скачал драйвер для ATI и при загрузке пишет не найден драйвер
Скачал драйвер для ATI и при загрузке пишет ненайден драйвер поиска,что делать7


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
30
Ответ Создать тему
Новые блоги и статьи
Отправка уведомления на почту при изменении наименования справочника
Maks 24.03.2026
Программная отправка письма электронной почты на примере изменения наименования типового справочника "Склады" в конфигурации БП3. Перед реализацией необходимо выполнить настройку системной учетной. . .
модель ЗдравоСохранения 5. Меньше увольнений- больше дохода!
anaschu 24.03.2026
Теперь система здравосохранения уменьшает количество увольнений. 9TO2GP2bpX4 a42b81fb172ffc12ca589c7898261ccb/ https:/ / rutube. ru/ video/ a42b81fb172ffc12ca589c7898261ccb/ Слева синяя линия -. . .
Midnight Chicago Blues
kumehtar 24.03.2026
Такой Midnight Chicago Blues, знаешь?. . Когда вечерние улицы становятся ночными, а ты не можешь уснуть. Ты идёшь в любимый старый бар, и бармен наливает тебе виски. Ты смотришь на пролетающие. . .
SDL3 для Desktop (MinGW): Вывод текста со шрифтом TTF с помощью библиотеки SDL3_ttf на Си и C++
8Observer8 24.03.2026
Содержание блога Финальные проекты на Си и на C++: finish-text-sdl3-c. zip finish-text-sdl3-cpp. zip
Жизнь в неопределённости
kumehtar 23.03.2026
Жизнь — это постоянное существование в неопределённости. Например, даже если у тебя есть список дел, невозможно дойти до точки, где всё окончательно завершено и больше ничего не осталось. В принципе,. . .
Модель здравоСохранения: работники работают быстрее после её введения.
anaschu 23.03.2026
geJalZw1fLo Корпорация до введения программа здравоохранения имела много невыполненных работниками заданий, после введения программы количество заданий выросло. Но на выплатах по больничным это. . .
Контроль уникальности заводского номера
Maks 23.03.2026
Алгоритм контроля уникальности заводского (или серийного) номера на примере нетипового документа выдачи шин для спецтехники с табличной частью, разработанного в конфигурации КА2. Данные берутся из. . .
Хочу заставить корпорации вкладываться в здоровье сотрудников: делаю мат модель здравосохранения
anaschu 22.03.2026
e7EYtONaj8Y Z4Tv2zpXVVo https:/ / github. com/ shumilovas/ med2. git
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru