URL: https://www.opennet.me/cgi-bin/openforum/vsluhboard.cgi
Форум: vsluhforumID9
Нить номер: 6075
[ Назад ]

Исходное сообщение
"Таймеры ядра Linux"

Отправлено Michelnok , 13-Янв-07 22:02 
Всем привет!

Два вопроса про динамические таймеры ядра Linux.

1. Можно ли освобождать память, занятую структурой таймера, в функции таймера?
Т.е. можно ли вызывать kfree(timer) из самой timer->function?

2. Делается ли какая-то блокировка на время выполнения timer->function и (она же) в mod_timer?
Иначе говоря, если я напишу в какой-нибудь mainline() что-то типа:

spin_lock(lock);
<делаем что-то>
mod_timer(timer,...);
spin_unlock(lock);

И в то же время в timer->function у меня будет что-то типа:

spin_lock(lock);
<делаем что-то>
spin_unlock(lock);

... то не загоню ли я себя в deadlock, когда timer->function будет ждать на spin_lock(lock), а захватившая этот lock mainline() - на mod_timer()?

Исходник kernel/timer.c смотрел. Мало что понял, всё очень запутано :(
Подозреваю что и kfree(timer) можно безопасно вызывать из timer->function, и никакого deadlock не будет (spin_unlock(timer->lock) вроде как вызывается перед вызовом timer->function в __run_timers), но уверености нет...

Заранее спасибо за ответы.


Содержание

Сообщения в этом обсуждении
"Таймеры ядра Linux"
Отправлено Michelnok , 18-Янв-07 20:35 
Эх... Придется сказать UP %-|



"Таймеры ядра Linux"
Отправлено timer , 18-Янв-07 20:39 
если вы аотом нигде не используете то можно
иначе спилить ветку на которой сидите - даже если вас страхуют снизу
чревато

"Таймеры ядра Linux"
Отправлено Michelnok , 19-Янв-07 14:39 
>если вы потом нигде не используете то можно

Я - нет. Главное чтобы ядро не пыталось обратиться к структуре таймера начиная с момента вызова timer->function.

>иначе спилить ветку на которой сидите - даже если вас страхуют снизу чревато

Знаю.
Мне его просто больше негде удалять. Никаких событий (ни прерываний, ни запросов), связанных с тем чему мне надо сделать kfree, больше не будет.
Ну не делать же периодическую сборку мусора :-)


"Таймеры ядра Linux"
Отправлено BigHo , 19-Янв-07 11:53 
Как понял речь идет о двух разных замках. deadlock блокировка в обычном случае может возникнуть в случае, если замки блокируются:
- в первом потоке - сперва lock1, и под его прикрытием - lock2;
- во втором потоке поряд обратый - сперва lock2, затем - lock1.

в этом случае они начинают бесконечно ожидать завершение друг друга, не имея шансов на прерывание, потому как высвободить _успешно_ заблокированный замок в режиме ожидания разблокировки замка они не могут.

Это касается userlevel блокировок для любой ОС. Механизм ядра в этом смысле очень похож. Но возможны некоторые ньюансы, напрямую не связанные с проблемой взавимоисключающих блокировок. Если уже есть какая-то проблема, то опиши сиптоматику.


"Таймеры ядра Linux"
Отправлено Michelnok , 19-Янв-07 14:34 
>Как понял речь идет о двух разных замках.

Да, один замок мой, я его захватываю как в mainline(), так и в timer->function. Вот о наличии второго я и спрашиваю. Дело в том что в структуре timer_list есть свой замок и он, естественно, используется ядром при операциях с таймером.

Насколько я понял из кода в kernel/timer.c, замок самого таймера не захватывается на время вызова timer->function. А в функции __mod_timer используется какая-то мутная последовательность блокировок, которая как раз и предназначена для избежания deadlock'а в ситуациях, похожих на мою (реализация несколько раз менялась уже в 2.6.*).


"Таймеры ядра Linux"
Отправлено BigHo , 19-Янв-07 16:20 
>>Как понял речь идет о двух разных замках.
>
>Да, один замок мой, я его захватываю как в mainline(), так и в timer->function. Вот о наличии второго я и спрашиваю. Дело в том что в структуре timer_list есть свой замок и он, естественно, используется ядром при операциях с таймером.
>Насколько я понял из кода в kernel/timer.c, замок самого таймера не захватывается на время вызова timer->function. А в функции __mod_timer используется какая-то мутная последовательность блокировок, которая как раз и предназначена для избежания deadlock'а в ситуациях, похожих на мою (реализация несколько раз менялась уже в 2.6.*).

Дополнительный вопрос по теме: в теле callback-функции таймера допустимо использовать spin_trylock механизм, вместо обычного spin_lock ? Если да, то в этом случае есть вариант обойти это гипотетически возможную проблему изменив режим блокировок.



"Таймеры ядра Linux"
Отправлено Michelnok , 19-Янв-07 17:59 
>Дополнительный вопрос по теме: в теле callback-функции таймера допустимо
> использовать spin_trylock механизм, вместо обычного spin_lock ?

Да, пожалуй так и сделаю для надежности (с соответствующей обработкой этой ситуации снаружи). А то не факт что внутренняя реализация таймеров не поменяется...


"Таймеры ядра Linux"
Отправлено BigHo , 20-Янв-07 15:06 
>>Дополнительный вопрос по теме: в теле callback-функции таймера допустимо
>> использовать spin_trylock механизм, вместо обычного spin_lock ?
>
>Да, пожалуй так и сделаю для надежности (с соответствующей обработкой этой ситуации
>снаружи). А то не факт что внутренняя реализация таймеров не поменяется...

Я просто хотел описать одну древнюю хитрость: не входить рекурсивно в функцию с установленной блокировкой, а изменять некую переменную в структуре, которую _всегда_ изменяют только с установленной блокировкой. Таким образом можно реализовать функциональность spin_trylock/spin_unlock (spin_lock - невозможна в общем случае). Примерный код:


.   int
.   my_mutex_trylock(struct my_mutex *mtx) {
.       int ret = 0;
.
.       spin_lock(&mtx->mtx_lock);
.       if (mtx->mtx_lock_count < 0)
.           ret = -1; // замок уже кто-то заблокировал
.       spin_unlock(&mtx->mtx_lock);
.       return ret;
.   }

.   void
.   my_mutex_unlock(struct my_mutex *mtx) {
.       mtx->mtx_lock_count = 0;
.       spin_unlock(&mtx->mtx_lock);
.   }


"Таймеры ядра Linux"
Отправлено BigHo , 20-Янв-07 15:51 
для

>.   my_mutex_trylock(struct my_mutex *mtx) {
>.       int ret = 0;
>.
>.       spin_lock(&mtx->mtx_lock);
>.       if (mtx->mtx_lock_count < 0)
>.           ret = -1; // замок уже кто-то заблокировал

тут конечно же нужно добавить:

.        else
.            mtx->mtx_lock_count = -1;

>.       spin_unlock(&mtx->mtx_lock);
>.       return ret;
>.   }
>


"Таймеры ядра Linux"
Отправлено Michelnok , 20-Янв-07 16:21 
>.   void
>.   my_mutex_unlock(struct my_mutex *mtx) {
>.       mtx->mtx_lock_count = 0;
>.       spin_unlock(&mtx->mtx_lock);
>.   }

Не, тут что-то не так. spin_unlock делать нечему, мы же не захватывали mtx->mtx_lock между вызовами наших try_lock/unlock.
Я бы вообще реализовал эти функции так:

atomic_t my_mytex=1;

int my_mutex_trylock(atomic_t* mtx)
{
  return !atomic_dec_and_test(mtx); // 0 если у нас получилось "захватить" замок
}

void my_mutext_unlock(atomic_t* mtx)
{
  atomic_inc(mtx);
}


"Таймеры ядра Linux"
Отправлено Michelnok , 20-Янв-07 16:24 

>int my_mutex_trylock(atomic_t* mtx)
>{

Тормоз я. Конечно же, тут:

  if(atomic_dec_and_test(mtx))
    return 0;
  atomic_inc(mtx);
  return -1;

>}


"Таймеры ядра Linux"
Отправлено BigHo , 21-Янв-07 16:23 
так получается реализация подобная userspace вызову
pthread_rwlock_tryrdlock/pthread_rwlock_unlock. Реализацию, близкую к pthread_trylock/pthread_unlock имеют вызовы pthread_rwlock_trywrlock/pthread_rwlock_unlock.

Хотя речь идет о вызовах ядра, можно реализовать тройку вызовов, близких к:
- pthread_rwlock_tryrdlock (my_lock_rd);
- pthread_rwlock_trywrlock (my_lock_wr);
- pthread_rwlock_unlock (my_unlock_rd и my_unlock_wr).

struct my_lock {
.   ...
.   int mtx_lock_count; // если < 0, значит открыт на запись,
.                       // если > 0, то открыт на чтение,
.                       // если == 0, свободен от блокировок.
.   spinlock_t mtx_lock;
.   ...
};

// смысл mtx_lock_count в том, что читающих процессов может быть несколько,
// и только один - записывающий. Можно договориться использовать значение -1 в
// качестве обозначения блокировки на запись, и любое положительное число -
// блокировка на чтение.

int
my_lock_rd(struct my_lock *mtx) {
.   int ret = 0;
.
.   spin_lock(&mtx->mtx_lock);
.   if (mtx->mtx_lock_count >= 0)
.       mtx->mtx_lock_count++;
.   else
.       ret = mtx->mtx_lock_count;
.   spin_unlock(&mtx->mtx_lock);
.   return ret;
}

int
my_lock_wr(struct my_lock *mtx) {
.   int ret = 0;
.
.   spin_lock(&mtx->mtx_lock);
.   ret = mtx->mtx_lock_count; // вернем информацию о том, какая блокировка
.                              // имеет место быть. В нашем случае, ошибкой
.                              // является ненулевой код возврата.
// чтобы произвести WRITE блокировку, замок должен быть свободен
// от любых блокировок. Иногда для WRITE используется термин
// "исключающая блокировка", а для READ - "конкурирующая блокировка".
.   if (mtx->mtx_lock_count == 0)
.       mtx->mtx_lock_count = -1; // метка блокировки на запись.
.   spin_unlock(&mtx->mtx_lock);
.   return ret;
}

// разблокирование - существенно более легкая задача.
// можно произвести кучу проверок, но зачем ?

void
my_unlock_rd(struct my_lock *mtx) {
.   spin_lock(&mtx->mtx_lock);
.   mtx->mtx_lock_count--;
.   spin_unlock(&mtx->mtx_lock);
}

void
my_unlock_wr(struct my_lock *mtx) {
.   spin_lock(&mtx->mtx_lock);
.   mtx->mtx_lock_count = 0;
.   spin_unlock(&mtx->mtx_lock);
}

У тебя используется глобальная переменая. Это будет хорошо работать, если замок нужен только один. В других случаях лучше использовать метод блокировки, подобный данному.


"Таймеры ядра Linux"
Отправлено BigHo , 21-Янв-07 18:11 
.. и завершаю свою мысль :)

atomic_* использует те же MUTEX блокировки, что и в предложенном способе, при этом обеспечивает меньший функционал (только READ или WRITE блокировка), и менее понятна (IMHO)..


"Таймеры ядра Linux"
Отправлено BigHo , 19-Янв-07 16:24 
> А в функции __mod_timer используется какая-то мутная последовательность блокировок, которая как раз и предназначена для избежания deadlock'а в ситуациях, похожих на мою (реализация несколько раз менялась уже в 2.6.*).

в каком файле определена эта функция ?


"Таймеры ядра Linux"
Отправлено BigHo , 19-Янв-07 16:54 
>> А в функции __mod_timer используется какая-то мутная последовательность блокировок, которая как раз и предназначена для избежания deadlock'а в ситуациях, похожих на мою (реализация несколько раз менялась уже в 2.6.*).

нашел, почитал. На выходе __mod_timer ни один замок не остается запертым. Т.е. на этот замок имеешь право не обращать внимание, поскольку для данного потока и данной callback функций этот замок всегда открыт (я посмотрел код только для __mod_timer. То что вне неё - не на моей совести). В этом случае deadlock исключен.