Комментарии 29
Единственное, чего не хватает - это ссылки с примером. По мне самым наглядным будет пример драйвера для USB-устройства.
/* Structure to hold all of our device specific stuff */
struct usb_skel {
struct usb_device *udev; /* the usb device for this device */
struct usb_interface *interface; /* the interface for this device */
struct semaphore limit_sem; /* limiting the number of writes in progress */
struct usb_anchor submitted; /* in case we need to retract our submissions */
struct urb *bulk_in_urb; /* the urb to read data with */
unsigned char *bulk_in_buffer; /* the buffer to receive data */
size_t bulk_in_size; /* the size of the receive buffer */
size_t bulk_in_filled; /* number of bytes in the buffer */
size_t bulk_in_copied; /* already copied to user space */
__u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
__u8 bulk_out_endpointAddr; /* the address of the bulk out endpoint */
int errors; /* the last request tanked */
bool ongoing_read; /* a read is going on */
spinlock_t err_lock; /* lock for errors */
struct kref kref;
struct mutex io_mutex; /* synchronize I/O with disconnect */
unsigned long disconnected:1;
wait_queue_head_t bulk_in_wait; /* to wait for an ongoing read */
};
Видим и семафор, и мьютекс и spinlock.
и
unsigned long disconnected:1;
битовое поле - какой-то легаси флаг наверно ;)
Совсем нет. USB устройство потенциально может быть извлечено в любой момент. В частности в момент выполнения операции write. И чем хорош Linux- так это тем, что даже в совершенно "учебном" варианте драйвера мы этот момент обязательно предусматриваем.
static ssize_t skel_write(struct file *file, const char *user_buffer,
size_t count, loff_t *ppos)
{
...
/* this lock makes sure we don't submit URBs to gone devices */
mutex_lock(&dev->io_mutex);
if (dev->disconnected) { /* disconnect() was called */
mutex_unlock(&dev->io_mutex);
retval = -ENODEV;
goto error;
}
...
return writesize;
...
error:
if (urb) {
usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma);
usb_free_urb(urb);
}
up(&dev->limit_sem);
exit:
return retval;
}
А еще здесь как видите вполне себе используется goto. Что давно признак дурного тона в прикладном программировании. Так что не только битовые поля.
Что давно признак дурного тона в прикладном программировании.
… исключительно по причине всеобщего помешательства
У каждого своя правда. Кому-то сильно нравится и ВерблюжийКод и названия функций типа bMutexLockFromISR().
Не думаю что в очередной раз стоит обсуждать вкус фломастеров. Самое главное чтоб в пределах одного проекта код был написан в одном стиле. И, к слову, это первая причина по которой код отказываются принимать в основную ветку. И именно она похоронила очень много полезного функционала ядра.
Так что если кто из авторов решит осчастливить своим кодом остальное человечество - имейте в виду, что сделать checkpatch перед отправкой в ваших интересах. Как и соблюдение правил оформления кода, принятых при проектировании ядра.
По-моему, если бы в Си был механизм отложенной деинициализации (как деструкторы в плюсах в RAII или контекстные менеджеры в питоне), все эти goto в ядре были бы не нужны
вообще-то, goto - это не дурной тон, а очень мощный оператор.
Он позволяет из любого места перепрыгнуть куда угодно.
Как всякое мощное оружие, goto не рекомендовали применять начинающим программистам.
Поэтому и придумали сказку про дурной тон.
Не, тег <саркамз> надо указывать явно... Но да ладно. Все операторы равны, но некоторые равнее, а некоторые... Что до goto - то ядро самый наглядный пример его правильного использования. Да, это в некоторой степени наследие терминала 80x25 символов (как и практически весь приинятый CodingStyle), но тем не менее.
Простите, а каким образом goto относится к ограничениям терминала?
Открываем CodyngStyle. Читаем пункт "1) Indentation"
Tabs are 8 characters, and thus indentations are also 8 characters.
Далее смотрим пункт "2) Breaking long lines and strings"
The preferred limit on the length of a single line is 80 columns.
Опять запоминаем. К слову сейчас это "preferred", но некоторое время назад было обязательным. И опускаемся ниже к пункту "3) Placing Braces and Spaces". И вот тут задумываемся... Берем ту же функцию write.
static ssize_t skel_write(struct file *file, const char *user_buffer,
size_t count, loff_t *ppos)
{
...
/* verify that we actually have some data to write */
if (count == 0)
goto exit;
...
if (!(file->f_flags & O_NONBLOCK)) {
if (down_interruptible(&dev->limit_sem)) {
retval = -ERESTARTSYS;
goto exit;
}
} else {
if (down_trylock(&dev->limit_sem)) {
retval = -EAGAIN;
goto exit;
}
}
...
if (retval < 0)
goto error;
...
if (retval) {
dev_err(&dev->interface->dev,
"%s - failed submitting write urb, error %d\n",
__func__, retval);
goto error_unanchor;
}
...
return writesize;
error_unanchor:
usb_unanchor_urb(urb);
error:
if (urb) {
usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma);
usb_free_urb(urb);
}
up(&dev->limit_sem);
exit:
return retval;
}
А теперь задумаемся - допустим мы хотим отказаться от goto. Как говорят сторонники такого подхода - нет ничего невозможного. И это истинная правда. Но правда еще и в том, что каждый отказ от goto будет на один уровень увеличивать вложенность. А мы помним - каждый уровень вложенности - это 8 колонок. В функции write 9 операторов goto. Т.е. мы имеем 9*8=72 колонки занятые на пустом месте только отступами. При рекомендуемых 80 на код у нас остается 8... И на экране откровенно линейный (при условии отсутствия ошибок) код превращается в "ёлочу".
При этом в итоговом бинарнике не будет никакой разницы. В отдельных случаях она может быть в пользу варианта с goto, но справедливости ради сегодня это экзотика.
Впрочем, если есть альтернативный взгляд я его с радостью выслушаю.
А откуда в этом примере возьмется столько уровней, если все goto
можно заменить на тот код, на который они указывают (только код под error:
вынести в отдельную функцию, чтобы не дублировать). Размер функции увеличится на несколько строк, но насколько это критично, учитывая, что она и так на один экран не влезает — не знаю.
Перепишите? Этот код вполне может быть изменен. А уж на более понятный и читаемый - абсолютно точно. Я бы посмотрел.
Пока не очень понимаю, в чём подвох (если он есть), но, если позволите, возьму из этого же файла функцию поменьше (для функции write подход тот же, только, как я выше писал, нужно ещё будет код под error:
вынести в отдельную функцию)
static int skel_open(struct inode *inode, struct file *file)
{
struct usb_skel *dev;
struct usb_interface *interface;
int subminor;
int retval = 0;
subminor = iminor(inode);
interface = usb_find_interface(&skel_driver, subminor);
if (!interface) {
pr_err("%s - error, can't find device for minor %d\n",
__func__, subminor);
return -ENODEV;
}
dev = usb_get_intfdata(interface);
if (!dev)
return -ENODEV;
retval = usb_autopm_get_interface(interface);
if (!retval) {
/* increment our usage count for the device */
kref_get(&dev->kref);
/* save our object in the file's private structure */
file->private_data = dev;
}
return retval;
}
А нет подвоха.
Можете оформлять патч и получать красивую строчку в резюме (еще и с пруфлинком). По мне так вполне хорошо. Но open() все же сильно проще, чем write(). А если делать, то делать единообразно. Т.е. такой подход должен быть как минимум во всем файле. Ну и у maintainer'ов может быть своя аргументация на тему "почему нет". В первом приближении две точки выхода из функции с одинаковым возвратным значением. Но мотивировать почему это плохо я не возьмусь.
В целом использование оператора goto в таком стиле является общепринятым. Но новый код, оформленный как у вас имеет все шансы быть одобренным.
У вас готофобия, причаститесь!
unsigned long disconnected:1;
битовое поле - какой-то легаси флаг наверно ;)
К слову, что называется. Вопрос для обсуждения - почему для "какого-то легаси флага" размером в один единственный бит выбран такой длинный базовый тип? Т.е. почему unsigned long, а не наличествующие рядом __u8, int или даже bool?
Сразу предупреждаю - у меня только версия, которая возникла исходя из опыта работы с ядром. И я не уверен в ее абсолютной правильности.
Интересно почитать, но как не специалисту в ядре не понятно в чем отличие up() от unlock(), почему один можно вызвать из прерывания, а другой нет? Правильно я понимаю, что поток ждущий на семафоре просто посыпается, проверяет и опять делает yield() или что-то такое, а unlock() сигнализирует планировщику что кого-то надо разбудить?
Если действительно интересно, то рекомендую для начала Linux Kerne 2.4 Internals (издавалась на русском, но свой экземпляр увы... потому прошу помощи зала). Там применительно к 2.4 разница между spinlock'ами и семафорами. А затем вооружившись полученными сведениями посмотреть реализацию в современных ядрах. Подробный ответ скорее всего потребует не одной статьи.
Если же совсем кратко то ваше предположение верно.
Условный yield, который делает ожидание семафора, переводит статус текущего процесса в "спящий", что означает что планировщик задач не будет его рассматривать в качестве кандидата на запуск. И далее вызывается планировщик процессов, который выбирает следующий процесс из числа готовых к выполнению. А операция разблокирования семафора обратно переключает статус процесс на "готов к выполению". То есть в какой-то момент планировщик выберет наш первый процесс и он продолжит выполнение с той же точки.
Так вот, обработчик прерывания - это совершенно другой случай, планировщик задач их не контролирует. Нет механизма запомнить точку в обработчике прерывания, поделать что-то еще и вернуться туда же (кроме явного вызова какой-то функции :)).
Ну и ко всему внутри обработчика прерывания прерывания на этом процессоре выключены. То есть при попытке поспать этот процессор просто зависнет навсегда.
Да, совсем плох стал...
Вопрос-то явно из FAQ и быть не может чтоб он нигде не задокументирован был. И конечно он довольно серьезно разжеван.
Первое, что стоит посмотреть из старенького Linux Kernel Memory Barriers и чуть более современного Unreliable Guide To Locking. Потом можно глянуть тут описаны механизмы блокировок в ядре (актуальная текстовая версия всегда с исходниками ядра). Опять же - внутренне устройство всегда можно посмотреть непосредственно в исходниках. Настоятельно рекомендую вот этот ресурс - прямо клад для разработчика ядра. В нем достаточно легко отыскиваются нужные функции. Например та же sema_init.
Единственное что не сильно хорошо описано, так это счетные семафоры. В контексте блокировок они действительно упоминаются редко. Но тут как раз стоит обратить внимание на указанный драйвер. В нем как раз с помощью счетного семафора ограничивается размер очереди на отправку данных (что разумно, ибо если ее не ограничивать легко можно съесть всю доступную память). И сделано это достаточно просто и понятно.
Семафоры в Linux медленно сходят со сцены