Аватар для peter_irich
370 / 226 / 53
Регистрация: 18.10.2017
Сообщений: 2,399

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

12.06.2018, 23:06. Показов 2883. Ответов 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
370 / 226 / 53
Регистрация: 18.10.2017
Сообщений: 2,399
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
370 / 226 / 53
Регистрация: 18.10.2017
Сообщений: 2,399
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
370 / 226 / 53
Регистрация: 18.10.2017
Сообщений: 2,399
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
370 / 226 / 53
Регистрация: 18.10.2017
Сообщений: 2,399
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
370 / 226 / 53
Регистрация: 18.10.2017
Сообщений: 2,399
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
370 / 226 / 53
Регистрация: 18.10.2017
Сообщений: 2,399
25.09.2018, 18:09  [ТС]
Я ошибся, сообщение о конфликте функций snprintf и других - это только предупреждения,
а ошибки дальше. 1-я из них - о несовпадении типов параметров.
Oжидается параметр типа
const struct timespec64 *
а передаётся параметр типа
const struct timespec *
0
 Аватар для peter_irich
370 / 226 / 53
Регистрация: 18.10.2017
Сообщений: 2,399
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
Ответ Создать тему
Опции темы

Новые блоги и статьи
Асинхронный приём данных из COM-порта
Argus19 01.05.2026
Асинхронный приём данных из COM-порта Купил на aliexpress термопринтер QR701. Он оказался странным. Поключил к Arduino Nano. Был очень удивлён. Наотрез отказывается печатать русские буквы. Чтобы. . .
попытка написать игровой сервер на C++
pyirrlicht 29.04.2026
попытка написать игровой сервер на плюсах с открытым бесконечным миром. возможно получится прикрутить интерпретатор питон для кастомизации игровой логики. что есть на текущий момент:. . .
Контроль уникальности выбранного документа-основания при изменении реквизита
Maks 28.04.2026
Алгоритм из решения ниже разработан на примере нетипового документа "ЗаявкаНаРемонтСпецтехники", разработанного в КА2. Задача: уведомлять пользователя, если указанная заявка (документ-основание). . .
Благородство как наказание
Maks 24.04.2026
У хорошего человека отношения с женщинами всегда складываются трудно. А я человек хороший. Заявляю без тени смущения, потому что гордиться тут нечем. От хорошего человека ждут соответствующего. . .
Валидация и контроль данных табличной части документа перед записью
Maks 22.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа, разработанного в КА2. Задача: контроль и валидация данных табличной части документа перед записью с учетом регламента компании. . .
Отчёт о затраченных материалах за определенный период с макетом печатной формы
Maks 21.04.2026
Отчёт из решения ниже размещён в конфигурации КА2. Задача: разработка отчёта по затраченным материалам за определённый период, с возможностью вывода печатной формы отчёта с шапкой и подвалом. В. . .
Отчёт о спецтехнике находящейся в ремонте
Maks 20.04.2026
Отчёт из решения ниже размещен в конфигурации КА2. Задача: отобразить спецтехнику, которая на данный момент находится в ремонте. Есть нетиповой документ "Заявка на ремонт спецтехники" который. . .
Памятка для бота и "визитка" для читателей "Semantic Universe Layer (Слой семантической вселенной)"
Hrethgir 19.04.2026
Сгенерировано для краткого описания по случаю сборки и компиляции скелета серверного приложения. И пусть после этого скажут, что статьи сгенерированные AI - туфта и не интересно. И это не реклама -. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru