Всем привет!Два вопроса про динамические таймеры ядра 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), но уверености нет...Заранее спасибо за ответы.
Эх... Придется сказать UP %-|
если вы аотом нигде не используете то можно
иначе спилить ветку на которой сидите - даже если вас страхуют снизу
чревато
>если вы потом нигде не используете то можноЯ - нет. Главное чтобы ядро не пыталось обратиться к структуре таймера начиная с момента вызова timer->function.
>иначе спилить ветку на которой сидите - даже если вас страхуют снизу чревато
Знаю.
Мне его просто больше негде удалять. Никаких событий (ни прерываний, ни запросов), связанных с тем чему мне надо сделать kfree, больше не будет.
Ну не делать же периодическую сборку мусора :-)
Как понял речь идет о двух разных замках. deadlock блокировка в обычном случае может возникнуть в случае, если замки блокируются:
- в первом потоке - сперва lock1, и под его прикрытием - lock2;
- во втором потоке поряд обратый - сперва lock2, затем - lock1.в этом случае они начинают бесконечно ожидать завершение друг друга, не имея шансов на прерывание, потому как высвободить _успешно_ заблокированный замок в режиме ожидания разблокировки замка они не могут.
Это касается userlevel блокировок для любой ОС. Механизм ядра в этом смысле очень похож. Но возможны некоторые ньюансы, напрямую не связанные с проблемой взавимоисключающих блокировок. Если уже есть какая-то проблема, то опиши сиптоматику.
>Как понял речь идет о двух разных замках.Да, один замок мой, я его захватываю как в mainline(), так и в timer->function. Вот о наличии второго я и спрашиваю. Дело в том что в структуре timer_list есть свой замок и он, естественно, используется ядром при операциях с таймером.
Насколько я понял из кода в kernel/timer.c, замок самого таймера не захватывается на время вызова timer->function. А в функции __mod_timer используется какая-то мутная последовательность блокировок, которая как раз и предназначена для избежания deadlock'а в ситуациях, похожих на мою (реализация несколько раз менялась уже в 2.6.*).
>>Как понял речь идет о двух разных замках.
>
>Да, один замок мой, я его захватываю как в mainline(), так и в timer->function. Вот о наличии второго я и спрашиваю. Дело в том что в структуре timer_list есть свой замок и он, естественно, используется ядром при операциях с таймером.
>Насколько я понял из кода в kernel/timer.c, замок самого таймера не захватывается на время вызова timer->function. А в функции __mod_timer используется какая-то мутная последовательность блокировок, которая как раз и предназначена для избежания deadlock'а в ситуациях, похожих на мою (реализация несколько раз менялась уже в 2.6.*).Дополнительный вопрос по теме: в теле callback-функции таймера допустимо использовать spin_trylock механизм, вместо обычного spin_lock ? Если да, то в этом случае есть вариант обойти это гипотетически возможную проблему изменив режим блокировок.
>Дополнительный вопрос по теме: в теле callback-функции таймера допустимо
> использовать spin_trylock механизм, вместо обычного spin_lock ?Да, пожалуй так и сделаю для надежности (с соответствующей обработкой этой ситуации снаружи). А то не факт что внутренняя реализация таймеров не поменяется...
>>Дополнительный вопрос по теме: в теле 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);
. }
для>. 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;
>. }
>
>. 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);
}
>int my_mutex_trylock(atomic_t* mtx)
>{Тормоз я. Конечно же, тут:
if(atomic_dec_and_test(mtx))
return 0;
atomic_inc(mtx);
return -1;>}
так получается реализация подобная 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);
}У тебя используется глобальная переменая. Это будет хорошо работать, если замок нужен только один. В других случаях лучше использовать метод блокировки, подобный данному.
.. и завершаю свою мысль :)atomic_* использует те же MUTEX блокировки, что и в предложенном способе, при этом обеспечивает меньший функционал (только READ или WRITE блокировка), и менее понятна (IMHO)..
> А в функции __mod_timer используется какая-то мутная последовательность блокировок, которая как раз и предназначена для избежания deadlock'а в ситуациях, похожих на мою (реализация несколько раз менялась уже в 2.6.*).в каком файле определена эта функция ?
>> А в функции __mod_timer используется какая-то мутная последовательность блокировок, которая как раз и предназначена для избежания deadlock'а в ситуациях, похожих на мою (реализация несколько раз менялась уже в 2.6.*).нашел, почитал. На выходе __mod_timer ни один замок не остается запертым. Т.е. на этот замок имеешь право не обращать внимание, поскольку для данного потока и данной callback функций этот замок всегда открыт (я посмотрел код только для __mod_timer. То что вне неё - не на моей совести). В этом случае deadlock исключен.