Setjmp установить точку длинного перехода


Содержание

Функция longjmp

Функция longjmp() возобновляет выполнение программы с места последнего обращения к функции setjmp() . Таким образом, функции longjmp() и setjmp() предоставляют средство передачи управления между функциями. Обратите внимание на необходимость включения заголовка .

Функция longjmp() восстанавливает состояние стека, сохраненное в буфере envbuf с помощью функции setjmp() . В результате выполнение программы возобновляется с оператора, следующего за вызовом функции setjmp() . Иначе говоря, компьютер «вводится в заблуждение»: «он считает», будто управление программой не выходило за пределы функции, которая вызвала функцию setjmpt() . (Выражаясь образно, функция longjmp() подобна многомерной машине пространства-времени. Она позволяет путешествовать во времени, не соблюдая какой бы то ни было последовательности событий: с ее помощью можно вернуться в «покинутый мир», не обращая внимания на то, что предварительно должен был быть произведен выход из вызванных функций. С ее помощью можно «вернуться домой», минуя промежуточные пункты. Она «искривляет» время и пространство (памяти) так, что с ее помощью можно попасть в покинутую точку программы, не выполняя нормальный процесс возврата из функции.)

Буфер evnbuf имеет тип jmp_buf , который определен в заголовке . Этот буфер должен быть заполнен в результате обращения к функции setjmp() еще до вызова функции longjmp() .

Значение параметра status становится возвращаемым значением функции setjmp() , и оно используется для того, чтобы определить «происхождение длинного перехода». Единственным недопустимым значением является нуль. Функция setjmp() возвращает нуль в том случае, когда она вызывается непосредственно программой, а не косвенно, т.е. путем выполнения функции longjmp() .

Функция longjmp() используется в основном для возврата из глубоко вложенного набора функций при возникновении ошибок.

Setjmp установить точку длинного перехода

Требования макроса тестирования свойств для glibc (см. feature_test_macros(7)):

setjmp(): Смотрите ЗАМЕЧАНИЯ.
sigsetjmp(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_C_SOURCE

ОПИСАНИЕ

Функция sigsetjmp() идентична setjmp(). Если значение savesigs не равно нулю, то текущая маска сигналов процесса сохраняется в env и будет восстановлена, если позднее будет запущена siglongjmp() с этим env.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

АТРИБУТЫ

Описание терминов данного раздела смотрите в attributes(7).

Интерфейс Атрибут Значение
setjmp(), sigsetjmp() безвредность в нитях безвредно (MT-Safe)

СООТВЕТСТВИЕ СТАНДАРТАМ

sigsetjmp(): POSIX.1-2001, POSIX.1-2008.

ЗАМЕЧАНИЯ

Если вы хотите сохранять и восстанавливать маску сигналов переносимым способом, то используйте sigsetjmp() и siglongjmp(3).

Функции setjmp() и sigsetjmp() делают программы трудными для восприятия и дальнейшей поддержки. По возможности используйте в работе другие методы.

Чем C ++ try / catch отличается от C setjmp / longjmp?

Я знал об обработке исключений в C++ с использованием try а также catch блоки. Я задавался вопросом, была ли эта функциональность там в C , Итак, теперь я знаю, что основная обработка ошибок в C сделано setjmp/longjmp ,

поскольку setjmp/longjmp нет в C++ Можно ли предположить, что try/catch лучше? В каких случаях.

Я мог бы реализовать try/catch функциональность в C с помощью setjmp/longjmp ,
Чем это отличается?

Решение

Я думаю, что главное отличие состоит в том, что try / catch знает об объектах в стеке и знает, как вызывать dtors для объектов, размещенных в стеке, и setjmp ничего с этим не делает.

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


Другие решения

try / catch будет учитывать RAII . Все объекты, которые покидают область видимости, будут должным образом уничтожены.

setjmp / longjmp не буду.

Несмотря на отсутствующие языковые функции, такие как RAII и т. Д., setjmp/longjmp принципиально отличается от механизма, используемого для бросания / отлова исключений. В наши дни исключения обрабатываются с использованием подхода с нулевой стоимостью, когда накладные расходы встречаются, если и только если исключение фактически выдается, а в противном случае нет накладных расходов. Поскольку предполагается, что в хорошем приложении исключения обычно не генерируются, это называется «нулевой стоимостью». С помощью setjmp / longjmp вы будете устанавливать точку перехода / контекст каждый раз, когда вы «входите в блок попытки». Следовательно, во время выполнения будет много накладных расходов только для установки точек перехода. В прошлом исключения были реализованы с использованием setjmp/longjmp (составителями, с RAII и всеми другими вещами, которые другие люди назвали «отсутствующими» — так что вы можете понять, почему их ответы не совсем верны), так что в теории вы можете добиться того же, но это будет намного хуже с точки зрения спектакль. Для получения более подробной информации о реализации обработки исключений, пожалуйста, обратитесь к Itanium C ++ ABI: обработка исключений .

В то время как setjmp/longjmp может или не может работать с деструкторами, это не главное отличие с точки зрения дизайна. Важно то, что когда вы бросаете исключение, вы не знаете или не заботитесь о том, где оно будет обработано. Реализация обходит стек вызовов до тех пор, пока не найдет предложение catch, которое может обработать брошенный тип, или пока оно не достигнет вершины стека; в последнем случае программа прерывается.

Я мог бы реализовать функцию try / catch / finally в C, используя setjmp / longjmp. Чем это отличается?

Это ответ на вопрос (вам не нужно делать это самостоятельно).

Безусловные переходы

Логика и организация программ

В систему команд ассемблера входит обычный набор команд перехода: безусловные и условные переходы, переходы с возвратами и др. Однако в ассемблере эти команды имеют некоторые особенности, которые здесь и рассматриваются.

Абсолютный адрес команды, которая должна быть выполнена следующей, определяется парой CS:IP, поэтому выполнение перехода означает изменение этих регистров, обоих или только одного (IP). Если изменяется только счетчик команд IP, то такой переход называется внутрисегментным или близким (управление остается в том же сегменте команд), а если меняются оба регистра CS и IP, то это межсегментный или дальний переход (начинают выполняться команды из другого сегмента команд). По способу изменения счетчика команд переходы делятся на абсолютные и относительные. Если в команде перехода указан адрес (смещение) той команды, которой надо передать управление, то это абсолютный переход. Однако в команде может быть указана величина (сдвиг), которую надо добавить к текущему значению регистра IP, чтобы получился адрес перехода, и тогда это будет относительный переход; при этом сдвиг может быть положительным и отрицательным, так что возможен переход вперед и назад. По величине сдвига относительные переходы делятся на короткие (сдвиг задается байтом) и длинные (сдвиг — слово). Абсолютные же переходы делятся на прямые и косвенные: при прямом переходе адрес перехода задается в самой команде, а при косвенном — в команде указывается регистр или ячейка памяти, в котором (которой) находится адрес перехода.

В ассемблере все команды безусловного перехода обозначаются одинаково:

но в зависимости от типа операнда, ассемблер формирует разные машинные команды.

1) Внутрисегментный относительный короткий переход.

Здесь i8 обозначает непосредственный операнд размеров в байт, который интерпретируется как знаковое целое от -128 до 127. Команда прибавляет это число к текущему значению регистра IP, получая в нем адрес (смещение) той команды, которая должна быть выполнена следующей. Регистр CS при этом не меняется.

Необходимо учитывать следующую особенность регистра IP. Выполнение любой команды начинается с того, что в IP заносится адрес следующей за ней команды, и только затем выполняется собственно команда. Для команды относительного перехода это означает, что операнд i8 прибавляется не к адресу этой команды, а к адресу команды, следующей за ней, поэтому, к примеру, команда JMP 0 — это переход на следующую команду программы.

При написании машинной программы сдвиги для относительных переходов приходится вычислять вручную, однако MASM избавляет от этого неприятного занятия: в MASM в командах относительного перехода всегда указывается метка той команды, на которую надо передать управление, и ассемблер сам вычисляет сдвиг, который он и записывает в машинную команду. Отсюда следует, что в MASM команда перехода по метке воспринимается не как абсолютный переход, а как относительный. По короткому переходу можно передать управление только на ближайшие команды программы — отстоящие от команды, следующей за командой перехода, до 128 байтов назад или до 127 байтов вперед. Для перехода на более дальние команды используется

2) Внутрисегментный относительный длинный переход.

JMP i16 (IP:=IP+i16)

Здесь i16 обозначает непосредственный операнд размером в слово, который рассматривается как знаковое целое от -32768 до 32767. Этот переход аналогичен короткому переходу.

Отметим, что, встретив команду перехода с меткой, которой была помечена одна из предыдущих (по тексту) команд программы, ассемблер вычисляет разность между адресом этой метки и адресом команды перехода и по этому сдвигу определяет, какую машинную команду относительного перехода — короткую или длинную — надо сформировать. Но если метка еще не встречалась в тексте программы, т.е. делается переход вперед, тогда ассемблер, не зная еще адреса метки, не может определить, какую именно машинную команду относительного перехода формировать, поэтому он на всякий случай выбирает команду длинного перехода. Однако эта машинная команда занимает 3 байта, тогда как команда короткого перехода — 2 байта, и если автор программы на MASM стремится к экономии памяти и знает заранее, что переход вперед будет на близкую метку, то он должен сообщить об этом ассемблеру, чтобы тот сформировал команду короткого перехода. Такое указание делается с помощью оператора SHORT:

Для переходов назад оператор SHORT не нужен: уже зная адрес метки, ассемблер сам определит вид команды относительного перехода.

3) Внутрисегментный абсолютный косвенный переход.

или JMP m16 (IP:=[m16])

Здесь r16 обозначает любой 16-битовый регистр общего назначения, а m16 — адрес слова памяти. В этом регистре (слове памяти) должен находиться адрес, по которому и будет произведен переход. Например, по команде JMP BX осуществляется переход по адресу, находящемуся в регистре BX.

4) Межсегментный абсолютный прямой переход.


JMP seg:ofs (CS:=seg, IP:=ofs)

Здесь seg — начало (первые 16 битов начального адреса) некоторого сегмента памяти, а ofs — смещение в этом сегменте. Пара seg:ofs определяет абсолютный адрес, по которому делается переход. В MASM эта пара всегда задается конструкцией FAR PTR , которая «говорит», что надо сделать переход по указанной метке, причем эта метка — «дальняя», из другого сегмента. Отметим, что ассемблер сам определяет, какой это сегмент, и сам подставляет в машинную команду его начало, т.е. seg.

5) Межсегментный абсолютный косвенный переход.

JMP m32 (CS:=[m32+2], IP:=[m32])

Здесь под m32 понимается адрес двойного слова памяти, в котором находится пара seg:ofs, задающая абсолютный адрес, по которому данная команда должна выполнить переход. Напомним, что в ПК величины размером в двойное слово хранятся в «перевернутом» виде, поэтому смещение ofs находится в первом слове двойного слова m32, а смещение seg — во втором слове (по адресу m32+2).

Команды межсегментного перехода используются тогда, когда команды программы размещены не в одном сегменте памяти, а в нескольких (например, команд столь много, что в совокупности они занимают более 64Кб, т.е. более максимального размера сегмента памяти). При переходе из одного такого сегмента в другой необходимо менять не только счетчик команд IP, но и содержимое регистра CS, загружая в последний начальный адрес второго сегмента. Такое одновременное изменение обоих этих регистров и делают команды межсегментного перехода.

При записи в ассемблере команд перехода следует учитывать, что они могут восприниматься неоднозначно. Скажем, как воспринимать команду

— как переход по метке A или как переход по адресу, хранящемуся в ячейке с именем A? Кроме того, какой это переход — внутрисегментный или межсегментный? Ответ зависит от того, как описано имя A, и от того, когда описано имя A — до или после команды перехода.

Илон Маск рекомендует:  Тег main

Пусть A описано до команды перехода («ссылка назад»). Если именем A помечена некоторая команда текущего сегмента команда (т.е. A — метка), тогда ассемблер формирует машинную команду внутрисегментного относительного перехода. Если же A — имя переменной, тогда ассемблер формирует машинную команду косвенного перехода — внутрисегментного, если A описано в директиве DW, или межсегментного, если A описано в директиве DD.

В случае же, если имя A описано после команды перехода («ссылка вперед»), ассемблер всегда формирует машинную команду внутрисегментного относительного длинного перехода. С учетом этого имя A обязательно должно метить команду из текущего сегмента команд, иначе будет зафиксирована ошибка. Если такая трактовка ссылки вперед не удовлетворяет автора программы, тогда он обязан с помощью оператора SHORT или PTR уточнить тип имени A:

JMP SHORT A ;внутрисегментный короткий переход по метке

JMP WORD PTR A ;внутрисегментный косвенный переход

JMP DWORD PTE A ;межсегментный косвенный переход

Отметим, что переход по метке A из другого сегмента команд всегда должен указываться с помощью FAR PTR (независимо от того, описана метка A до или после команды перехода):

JMP FAR PTR A ;межсегментный переход по метке

Не нашли то, что искали? Воспользуйтесь поиском:

Setjmp установить точку длинного перехода

6.5.8. Как уже было сказано, при exec все открытые файлы достаются в наследство новой программе (в частности, если между fork и exec были перенаправлены вызовом dup2 стандартные ввод и вывод, то они останутся перенаправленными и у новой программы). Что делать, если мы не хотим, чтобы наследовались все открытые файлы? (Хотя бы потому, что большинством из них новая программа пользоваться не будет — в основном она будет использовать лишь fd 0, 1 и 2; а ячейки в таблице открытых файлов процесса они занимают). Во-первых, ненужные дескрипторы можно явно закрыть close в промежутке между fork-ом и exec-ом. Однако не всегда мы помним номера дескрипторов для этой операции. Более радикальной мерой является тотальная чистка:

Есть более элегантный путь. Можно пометить дескриптор файла специальным флагом, означающим, что во время вызова exec этот дескриптор должен быть автоматически закрыт (режим file-close-on-exec — fclex):

Отменить этот режим можно так:

Здесь есть одна тонкость: этот флаг устанавливается не для структуры file — «открытый файл», а непосредственно для дескриптора в таблице открытых процессом файлов (массив флагов: char u_pofile[NOFILE]). Он не сбрасывается при закрытии файла, поэтому нас может ожидать сюрприз:

Если fd1 окажется равным fd, то дескриптор fd1 будет при exec-е закрыт, чего мы явно не ожидали! Поэтому перед close(fd) полезно было бы отменить режим fclex.

6.5.9. Каждый процесс имеет управляющий терминал (short *u_ttyp). Он достается процессу в наследство от родителя (при fork и exec) и обычно совпадает с терминалом, с на котором работает данный пользователь.

Каждый процесс относится к некоторой группе процессов (int p_pgrp), которая также наследуется. Можно послать сигнал всем процессам указанной группы pgrp:

посылает сигнал sig всем процессам, чья группа совпадает с группой посылающего процесса. Процесс может узнать свою группу:

а может стать «лидером» новой группы. Вызов


делает следующие операции:

В свою очередь, управляющий терминал тоже имеет некоторую группу (t_pgrp). Это значение устанавливается равным группе процесса, первым открывшего этот терминал:

Таким процессом обычно является процесс регистрации пользователя в системе (который спрашивает у вас имя и пароль). При закрытии терминала всеми процессами (что бывает при выходе пользователя из системы) терминал теряет группу: t_pgrp=0;

При нажатии на клавиатуре терминала некоторых клавиш:

драйвер терминала посылает соответственно сигналы SIGINT и SIGQUIT всем процессам группы терминала, т.е. как бы делает

Именно поэтому мы можем прервать процесс нажатием клавиши DEL. Поэтому, если процесс сделал setpgrp(), то сигнал с клавиатуры ему послать невозможно (т.к. он имеет свой уникальный номер группы != группе терминала).

Если процесс еще не имеет управляющего терминала (или уже его не имеет после setpgrp), то он может сделать любой терминал (который он имеет право открыть) управляющим для себя. Первый же файл-устройство, являющийся интерфейсом драйвера терминалов, который будет открыт этим процессом, станет для него управляющим терминалом. Так процесс может иметь каналы 0, 1, 2 связанные с одним терминалом, а прерывания получать с клавиатуры другого (который он сделал управляющим для себя).

Процесс регистрации пользователя в системе — /etc/getty (название происходит от «get tty» — получить терминал) — запускается процессом номер 1 — /etc/init-ом — на каждом из терминалов, зарегистрированных в системе, когда

  • система только что была запущена;
  • либо когда пользователь на каком-то терминале вышел из системы (интерпретатор команд завершился).

В сильном упрощении getty может быть описан так:

Здесь последовательные вызовы open занимают последовательные ячейки в таблице открытых процессом файлов (поиск каждой новой незанятой ячейки производится с начала таблицы) — в итоге по дескрипторам 0,1,2 открывается файл-терминал. После этого дескрипторы 0,1,2 наследуются всеми потомками интерпретатора команд. Процесс init запускает по одному процессу getty на каждый терминал, как бы делая

и ожидает окончания любого из них. После входа пользователя в систему на каком-то терминале, соответствующий getty превращается в интерпретатор команд (pid процесса сохраняется). Как только кто-то из них умрет — init перезапустит getty на соответствующем терминале (все они — его сыновья, поэтому он знает — на каком именно терминале).

6.6. Трубы и FIFO-файлы.

Процессы могут обмениваться между собой информацией через файлы. Существуют файлы с необычным поведением — так называемые FIFO-файлы (first in, first out), ведущие себя подобно очереди. У них указатели чтения и записи разделены. Работа с таким файлом напоминает проталкивание шаров через трубу — с одного конца мы вталкиваем данные, с другого конца — вынимаем их. Операция чтения из пустой «трубы» проиостановит вызов read (и издавший его процесс) до тех пор, пока кто-нибудь не запишет в FIFOфайл какие-нибудь данные. Операция позиционирования указателя — lseek() — неприме- нима к FIFO-файлам. FIFO-файл создается системным вызовом

где 0666 — коды доступа к файлу. При помощи FIFO-файла могут общаться даже неродственные процессы.

Разновидностью FIFO-файла является безымянный FIFO-файл, предназначенный для обмена информацией между процессом-отцом и процессом-сыном. Такой файл — канал связи как раз и называется термином «труба» или pipe. Он создается вызовом pipe:

Если бы файл-труба имел имя PIPEFILE, то вызов pipe можно было бы описать как

При вызове fork каждому из двух процессов достанется в наследство пара дескрипторов:

Пусть процесс A будет посылать информацию в процесс B. Тогда процесс A сделает:

Получаем в итоге:

Обычно поступают еще более элегантно, перенаправляя стандартный вывод A в канал conn[1]

а стандартный ввод B — из канала conn[0]

Это соответствует конструкции

записанной на языке СиШелл.

Файл, выделяемый под pipe, имеет ограниченный размер (и поэтому обычно целиком оседает в буферах в памяти машины). Как только он заполнен целиком — процесс, пишущий в трубу вызовом write, приостанавливается до появления свободного места в трубе. Это может привести к возникновению тупиковой ситуации, если писать программу неаккуратно. Пусть процесс A является сыном процесса B, и пусть процесс B издает вызов wait, не закрыв канал conn[0]. Процесс же A очень много пишет в трубу conn[1]. Мы получаем ситуацию, когда оба процесса спят:


A потому что труба переполнена, а процесс B ничего из нее не читает, так как ждет окончания A;

B потому что процесс-сын A не окончился, а он не может окончиться пока не допишет свое сообщение.

Решением служит запрет процессу B делать вызов wait до тех пор, пока он не прочитает ВСЮ информацию из трубы (не получит EOF). Только сделав после этого close(conn[0]); процесс B имеет право сделать wait.

Если процесс B закроет свою сторону трубы close(conn[0]) прежде, чем процесс A закончит запись в нее, то при вызове write в процессе A, система пришлет процессу A сигнал SIGPIPE — «запись в канал, из которого никто не читает».

6.6.1. Открытие FIFO файла приведет к блокированию процесса («засыпанию»), если в буфере FIFO файла пусто. Процесс заснет внутри вызова open до тех пор, пока в буфере что-нибудь не появится.

Чтобы избежать такой ситуации, а, например, сделать что-нибудь иное полезное в это время, нам надо было бы опросить файл на предмет того — можно ли его открыть? Это делается при помощи флага O_NDELAY у вызова open.

Если open ведет к блокировке процесса внутри вызова, вместо этого будет возвращено значение (-1). Если же файл может быть немедленно открыт — возвращается нормальный дескриптор со значением >=0, и файл открыт.

O_NDELAY является зависимым от семантики того файла, который мы открываем. К примеру, можно использовать его с файлами устройств, например именами, ведущими к последовательным портам. Эти файлы устройств (порты) обладают тем свойством, что одновременно их может открыть только один процесс (так устроена реализация функции open внутри драйвера этих устройств). Поэтому, если один процесс уже работает с портом, а в это время второй пытается его же открыть, второй «заснет» внутри open, и будет дожидаться освобождения порта close первым процессом. Чтобы не ждать — следует открывать порт с флагом O_NDELAY.

6.7. Нелокальный переход.

Теперь поговорим про нелокальный переход. Стандартная функция setjmp позволяет установить в программе «контрольную точку» * , а функция longjmp осуществляет прыжок в эту точку, выполняя за один раз выход сразу из нескольких вызванных функций (если надо) * . Эти функции не являются системными вызовами, но поскольку они реализуются машинно-зависимым образом, а используются чаще всего как реакция на некоторый сигнал, речь о них идет в этом разделе. Вот как, например, выглядит рестарт программы по прерыванию с клавиатуры:

setjmp возвращает 0 при запоминании контрольной точки. При прыжке в контрольную точку при помощи longjmp, мы оказываемся снова в функции setjmp, и эта функция возвращает нам значение второго аргумента longjmp, в этом примере — nsig.

Прыжок в контрольную точку очень удобно использовать в алгоритмах перебора с возвратом (backtracking): либо — если ответ найден — прыжок на печать ответа, либо если ветвь перебора зашла в тупик — прыжок в точку ветвления и выбор другой альтернативы. При этом можно делать прыжки и в рекурсивных вызовах одной и той же функции: с более высокого уровня рекурсии в вызов более низкого уровня (в этом случае jmp_buf лучше делать автоматической переменной — своей для каждого уровня вызова функции).

6.7.1. Перепишите следующий алгоритм при помощи longjmp.

Вот ответ, использующий нелокальный переход вместо цепочки return-ов:

Обратите внимание, что при возврате ответа через второй аргумент longjmp мы прибавили 1, а при печати ответа мы эту единицу отняли. Это сделано на случай ответа j==0, чтобы функция setjmp не вернула бы в этом случае значение 0 (признак установки контрольной точки).

Ответ: longjmp делает прыжок в функцию f(), из которой уже произошел возврат управления. При переходе в тело функции в обход ее заголовка не выполняются машинные команды «пролога» функции — функция остается «неактивированной». При возврате из вызванной таким «нелегальным» путем функции возникает ошибка, и программа падает. Мораль: в функцию, которая НИКЕМ НЕ ВЫЗВАНА, нельзя передавать управление. Обратный прыжок из f() в main() — был бы законен, поскольку функция main() является активной, когда управление находится в теле функции f(). Т.е. можно «прыгать» из вызванной функции в вызывающую: из f() в main() или в g(); и из g() в main();

но нельзя наоборот: из main() в g() или f(); а также из g() в f(). Можно также совершать прыжок в пределах одной и той же функции:

6.8. Хозяин файла, процесса, и проверка привелегий.

UNIX — многопользовательская система. Это значит, что одновременно на разных терминалах, подключенных к машине, могут работать разные пользователи (а может и один на нескольких терминалах). На каждом терминале работает свой интерпретатор команд, являющийся потомком процесса /etc/init.

Илон Маск рекомендует:  Прокрутка страницы с помощью скрипта на JavaScript (Титры)

6.8.1. Теперь — про функции, позволяющие узнать некоторые данные про любого пользователя системы. Каждый пользователь в UNIX имеет уникальный номер: идентификатор пользователя (user id), а также уникальное имя: регистрационное имя, которое он набирает для входа в систему. Вся информация о пользователях хранится в файле /etc/passwd. Существуют функции, позволяющие по номеру пользователя узнать регистрационное имя и наоборот, а заодно получить еще некоторую информацию из passwd:

Эти функции возвращают указатели на статические структуры, скрытые внутри этих функций. Структуры эти имеют поля:

Функции возвращают значение p==NULL, если указанный пользователь не существует (например, если задан неверный uid). uid хозяина данного процесса можно узнать вызовом getuid, а uid владельца файла — из поля st_uid структуры, заполняемой системным вызовом stat (а идентификатор группы владельца — из поля st_gid). Задание: модифицируйте наш аналог программы ls, чтобы он выдавал в текстовом виде имя владельца каждого файла в каталоге.

6.8.2. Владелец файла может изменить своему файлу идентификаторы владельца и группы вызовом

т.е. «подарить» файл другому пользователю. Забрать чужой файл себе невозможно. При этой операции биты S_ISUID и S_ISGID в кодах доступа к файлу (см. ниже) сбрасываются, поэтому создать «Троянского коня» и, сделав его хозяином суперпользователя, получить неограниченные привелегии — не удастся!

6.8.3. Каждый файл имеет своего владельца (поле di_uid в I-узле на диске или поле i_uid в копии I-узла в памяти ядра А. Богатырев, 1992-95 Си в UNIX * ). Каждый процесс также имеет своего владельца (поля u_uid и u_ruid в u-area). Как мы видим, процесс имеет два параметра, обозначающие владельца. Поле ruid называется «реальным идентификатором» пользователя, а uid «эффективным идентификатором«. При вызове exec() заменяется программа, выполняемая данным процессом:

Как видно из этой схемы, реальный идентификатор хозяина процесса наследуется. Эффективный идентификатор обычно также наследуется, за исключением одного случая: если в кодах доступа файла (i_mode) выставлен бит S_ISUID (set-uid bit), то значение поля u_uid в новом процессе станет равно значению i_uid файла с программой:


т.е. эффективным владельцем процесса станет владелец файла. Здесь gid — это идентификаторы группы владельца (которые тоже есть и у файла и у процесса, причем у процесса — реальный и эффективный).

Зачем все это надо? Во-первых затем, что ПРАВА процесса на доступ к какому-либо файлу проверяются именно для эффективного владельца процесса. Т.е. например, если файл имеет коды доступа

и владельца i_uid, то процесс, пытающийся открыть этот файл, будет «проэкзаменован» в таком порядке:

Процесс может узнать свои параметры:

а также установить их:

Рассмотрим вызов setuid. Он работает так (u_uid — относится к процессу, издавшему этот вызов):

Поле p_suid позволяет set-uid-ной программе восстановить эффективного владельца, который был у нее до exec-а.

Во-вторых, все это надо для следующего случая: пусть у меня есть некоторый файл BASE с хранящимися в нем секретными сведениями. Я являюсь владельцем этого файла и устанавливаю ему коды доступа 0600 (чтение и запись разрешены только мне). Тем не менее, я хочу дать другим пользователям возможность работать с этим файлом, однако контролируя их деятельность. Для этого я пишу программу, которая выполняет некоторые действия с файлом BASE, при этом проверяя законность этих действий, т.е. позволяя делать не все что попало, а лишь то, что я в ней предусмотрел, и под жестким контролем. Владельцем файла PROG, в котором хранится эта программа, также являюсь я, и я задаю этому файлу коды доступа 0711 (rwx—x—x) — всем можно выполнять эту программу. Все ли я сделал, чтобы позволить другим пользоваться базой BASE через программу (и только нее) PROG? Нет!

Если кто-то другой запустит программу PROG, то эффективный идентификатор процесса будет равен идентификатору этого другого пользователя, и программа не сможет открыть мой файл BASE. Чтобы все работало, процесс, выполняющий программу PROG, должен работать как бы от моего имени. Для этого я должен вызовом chmod либо командой

добавить к кодам доступа файла PROG бит S_ISUID.

После этого, при запуске программы PROG, она будет получать эффективный идентификатор, равный моему идентификатору, и таким образом сможет открыть и работать с файлом BASE. Вызов getuid позволяет выяснить, кто вызвал мою программу (и занести это в протокол, если надо).

Программы такого типа — не редкость в UNIX, если владельцем программы (файла ее содержащего) является суперпользователь. В таком случае программа, имеющая бит доступа S_ISUID работает от имени суперпользователя и может выполнять некоторые действия, запрещенные обычным пользователям. При этом программа внутри себя делает всяческие проверки и периодически спрашивает пароли, то есть при работе защищает систему от дураков и преднамеренных вредителей. Простейшим примером служит команда ps, которая считывает таблицу процессов из памяти ядра и распечатывает ее. Доступ к физической памяти машины производится через файл-псевдоустройство /dev/mem, а к памяти ядра /dev/kmem. Чтение и запись в них позволены только суперпользователю, поэтому программы «общего пользования», обращающиеся к этим файлам, должны иметь бит set-uid.

Откуда же изначально берутся значения u >

Затем он выполняет:

В результате он становится процессом пользователя, вошедшего в систему. Таковым же после exec-а, выполняемого getty, остается и интерпретатор команд p->pw_shell (обычно /bin/sh или /bin/csh) и все его потомки.

На самом деле, в описании регистрации пользователя при входе в систему, сознательно было допущено упрощение. Дело в том, что все то, что мы приписали процессу getty, в действительности выполняется двумя программами: /etc/getty и /bin/login.

Сначала процесс getty занимается настройкой параметров линии связи (т.е. терминала) в соответствии с ее описанием в файле /etc/gettydefs. Затем он запрашивает имя пользователя и заменяет себя (при помощи сисвызова exec) процессом login, передавая ему в качестве одного из аргументов полученное имя пользователя.

Затем login запрашивает пароль, настраивает окружение, и.т.п., то есть именно он производит все операции, приведенные выше на схеме. В конце концов он заменяет себя интерпретатором команд.

Такое разделение делается, в частности, для того, чтобы считанный пароль в случае опечатки не хранился бы в памяти процесса getty, а уничтожался бы при очистке памяти завершившегося процесса login. Таким образом пароль в истинном, незашифрованном виде хранится в системе минимальное время, что затрудняет его подсматривание средствами электронного или программного шпионажа. Кроме того, это позволяет изменять систему проверки паролей не изменяя программу инициализации терминала getty.

Имя, под которым пользователь вошел в систему на данном терминале, можно узнать вызовом стандартной функции

Эта функция не проверяет uid процесса, а просто извлекает запись про данный терминал из файла /etc/utmp.

Наконец отметим, что владелец файла устанавливается при создании этого файла (вызовами creat или mknod), и полагается равным эффективному идентификатору создающего процесса.

6.8.4. Напишите программу, узнающую у системы и распечатывающую: номер процесса, номер и имя своего владельца, номер группы, название и тип терминала на котором она работает (из переменной окружения TERM).

* — В некотором буфере запоминается текущее состояние процесса: положение вершины стека вызовов функций (stack pointer); состояние всех регистров процессора, включая регистр адреса текущей машинной команды (instruction pointer).

* — Это достигается восстановлением состояния процесса из буфера. Изменения, происшедшие за время между setjmp и longjmp в статических данных не отменяются (т.к. они не сохранялись).

* — При открытии файла и вообще при любой операции с файлом, в таблицах ядра заводится копия I-узла (для ускорения доступа, чтобы постоянно не обращаться к диску). Если I-узел в памяти будет изменен, то при закрытии файла (а также периодически через некоторые промежутки времени) эта копия будет записана обратно на диск. Структура I-узла в памяти — struct inode — описана в файле , а на диске — struct dinode — в файле .


© Copyright А. Богатырев, 1992-95
Си в UNIX

Linux программирование в примерах (130 стр.)

Почему полезен нелокальный переход? Рассмотрите интерактивную программу, которая считывает и выполняет программы. Предположим, пользователь запускает длительное задание, разочаровывается или меняет мнение о данном задании и нажимает CTRL-С для генерирования сигнала SIGINT . Когда запускается обработчик сигнала, он может перейти обратно в начало главного цикла чтения и обработки команд. Строковый редактор ed представляет простой пример этого:

$ ed -p ‘> ‘ sayings /* Запуск ed, ‘> ‘ используется как приглашение */

sayings: No such file or directory

> a /* Добавить текст */

^C /* Сгенерировать SIGINT */

? /* Сообщение об ошибке »один размер подходит всем» */

> 1,$p /* ed возвращается в командную строку */

Hello, world /* ‘1,$p’ prints all the lines */

> w /* Сохранить файл */

> q /* Все сделано */

Внутри себя ed устанавливает перед циклом команд точку возврата, и обработчик сигнала осуществляет нелокальный переход на эту точку возврата.

12.5.1. Использование стандартных функций: setjmp() и longjmp()

Нелокальные переходы осуществляются с помощью функций setjmp() и longjmp() . Эти функции используются в двух разновидностях. Традиционные процедуры определены стандартом ISO С:

int setjmp(jmp_buf env);

void longjmp(jmp_buf env, int val);

Тип jmp_buf определен через typedef в . setjmp() сохраняет текущее «окружение» в env . env обычно является глобальной или статической на уровне файла переменной, так что она может использоваться из вызванной функции. Это окружение включает любую информацию, необходимую для перехода на местоположение, из которого была вызвана setjmp() . Содержание jmp_buf по своей природе машинно-зависимо; таким образом, jmp_buf является непрозрачным типом: тем, что вы используете, не зная, что находится внутри него.

setjmp() возвращает 0, когда она вызывается для сохранения в jmp_buf текущего окружения. Ненулевое значение возвращается, когда с использованием окружения осуществляется нелокальный переход:

jmp_buf command_loop; /* На глобальном уровне */

if (setjmp(command_loop) == 0) /* Состояние сохранено, продолжить */

else /* Мы попадаем сюда через нелокальный переход */

printf(«?\n»); /* ed’s famous message */

/* . теперь начать цикл команд . */

longjmp() осуществляет переход. Первым параметром является jmp_buf , который должен быть инициализирован с помощью setjmp() . Второй является целым ненулевым значением, которое setjmp() возвращает в первоначальное окружение. Это сделано так, что код, подобный только что показанному, может различить установку окружения и прибытие путем нелокального перехода.

Стандарт С утверждает, что даже если longjmp() вызывается со вторым аргументом, равным 0, setjmp() по-прежнему возвращает ненулевое значение. В таком случае она возвращает 1.


Возможность передать целое значение и вернуться обратно из setjmp() полезна; это позволяет коду уровня пользователя различать причину перехода. Например, gawk использует эту возможность для обработки операторов break и continue внутри циклов. (Язык awk осознанно сделан похожим на С в своем синтаксисе для циклов, с использованием while , do-while , for , break и continue .) Использование setjmp() выглядит следующим образом (из eval.c в дистрибутиве gawk 3.1.3):

507 case Node_K_while:

508 PUSH_BINDING(loop_tag_stack, loop_tag, loop_tag_valid);

510 stable_tree = tree;

511 while (eval_condition(stable_tree->lnode)) <

513 switch (setjmp(loop_tag)) <

514 case 0: /* обычный не переход */

517 case TAG_CONTINUE: /* оператор continue */

519 case TAG_BREAK: /* оператор break */

520 RESTORE_BINDING(loop_tag_stack, loop_tag, loop_tag_valid);

526 RESTORE_BINDING(loop_tag_stack, loop_tag, loop_tag_valid);

Этот фрагмент кода представляет цикл while . Строка 508 управляет вложенными циклами посредством стека сохраненных переменных jmp_buf . Строки 511–524 выполняют цикл while (используя цикл С while !). Строка 511 проверяет условие цикла. Если оно истинно, строка 513 выполняет switch на возвращаемое значение setjmp() . Если оно равно 0 (строки 514–516), строка 515 выполняет тело оператора. Однако, когда setjmp() возвращает TAG_BREAK или TAG_CONTINUE , оператор switch обрабатывает их соответствующим образом (строки 517–518 и 519–521 соответственно).

Оператор break на уровне awk передает TAG_BREAK функции longjmp() , a continue уровня awk передает TAG_CONTINUE . Снова из eval.c с некоторыми пропущенными не относящимися к делу подробностями:

657 case Node_K_break:

675 longjmp(loop_tag, TAG_BREAK);

678 case Node_K_continue:

696 longjmp(loop_tag, TAG_CONTINUE);

Вы можете думать о setjmp() как об установке метки, а о longjmp() как выполнении goto с дополнительным преимуществом возможности сказать, откуда «пришел» код (по возвращаемому значению).

Илон Маск рекомендует:  Как получить hinstance консольного приложения

12.5.2. Обработка масок сигналов: sigsetjmp() и siglongjmp()

По историческим причинам, которые, скорее всего, утомили бы вас до слез, стандарт С 1999 г. ничего не говорит о влиянии setjmp() и longjmp() на состояние сигналов процесса, а POSIX явно констатирует, что их влияние на маску сигналов процесса (см. раздел 10.6 «Сигналы POSIX») не определено.

Другими словами, если программа изменяет свою маску сигналов процесса между первым вызовом setjmp() и вызовом longjmp() , каково состояние маски сигналов процесса после longjmp() ? Та ли эта маска, когда была впервые вызвана setjmp() ? Или это текущая маска? POSIX явно утверждает, что «нет способа это узнать».

Чтобы сделать обработку маски сигналов процесса явной, POSIX ввел две дополнительные функции и один typedef :

int sigsetjmp(sigjmp_buf env, int savesigs); /* Обратите внимание:

Как C ++ попытаться / поймать отличается от C setjmp / longjmp?

Я знал об обработке исключений в C++ использовании try и catch блоков. Интересно , если эта функция была там C . Итак, теперь я знаю , что основная ошибка обработки в C выполняется setjmp/longjmp .

Так как setjmp/longjmp нет в C++ , Можно предположить , что try/catch лучше? В каких случаях.


Я мог бы реализовать try/catch функциональные возможности в C использовании setjmp/longjmp . Как это отличается ??

try / catch Будет приходиться RAII . Все объекты , которые покидают сферу будут надлежащим образом уничтожены.

setjmp / longjmp Не будет.

Я думаю, что главное отличие заключается в том, что попробовать / поймать сознают объекты на стеке и знать, как вызвать dtors для объектов, выделенных на стеке, что setjmp ничего не делает с этим.

Кроме того, пользовательский интерфейс намного богаче, вы можете определить несколько типов исключений и ведет себя по-разному в зависимости от того

Несмотря на функцию недостающего языка как RAII и т.д., setjmp/longjmp принципиально отличается от механизма , используемым для метания / ловли исключений. В эти дни, исключения обрабатываются с использованием нулевой стоимости подхода , при котором накладные расходах встречаются тогда и только тогда , когда исключение фактически выброшены, а в противном случае нет никаких накладных расходов. Так как предполагается, что в хорошем исключения приложения обычно не кинули, это называется «нулевой стоимости». С setjmp / longjmp, вы будете устанавливать скачка точки / контекста каждый раз , когда вы «ввести TRY блок». Таким образом, будет много накладных расходов во время выполнения просто установить точки перехода. Назад в день, исключения были реализованы с использованием setjmp/longjmp (составителями, с RAII и все другие вещи , которые другие люди заявил , как «отсутствует» — так что вы можете понять , почему их ответы не совсем правильно), так что в теории вы можете достичь того же, но это будет гораздо хуже , с точки зрения представление. Для получения более подробной информации о выполнении обработки исключений , пожалуйста , обратитесь к Itanium C ++ ABI: Обработка исключений .

Хотя setjmp/longjmp может и не справиться с деструкторов, что это не важное отличие от точки зрения дизайна. Важно то , что когда вы бросаете исключение вы не знаете , или все равно , где он будет обрабатываться. Реализация идет стек вызовов до тех пор , пока не найдет пункт улова , который может обрабатывать брошенный тип или пока он не достигнет вершины стека; в последнем случае программа будет прервана.

Практическое использование setjmp и longjmp в C

Может ли кто-нибудь объяснить мне, где именно функции setjmp() и longjmp() могут использоваться практически во встроенном программировании? Я знаю, что они предназначены для обработки ошибок. Но я хотел бы знать некоторые варианты использования.

Обработка ошибок
Предположим, что в функции, вложенной во многие другие функции, есть ошибка, и обработка ошибок имеет смысл только в функции верхнего уровня.

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

Это ситуация, когда setjmp/longjmp имеет смысл. Эти ситуации похожи на ситуацию, когда смысл в других языках (С++, Java) имеет смысл.

Сопрограммы
Помимо обработки ошибок, я могу думать и о другой ситуации, когда вам нужен setjmp/longjmp в C:

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

Вот небольшой демонстрационный пример. Надеюсь, он удовлетворяет запросу Sivaprasad Palas для примера кода и отвечает на вопрос TheBlastOne, как setjmp/longjmp поддерживает реализацию корректов (насколько я вижу, он не основан на каком-то нестандартном или новом поведении).

EDIT:
Может быть, на самом деле это undefined поведение longjmp вниз по стоп-косту (см. Комментарий MikeMB, хотя я еще не имел возможности проверить это).

В следующем рисунке показан ход выполнения:

Предупреждающая записка
При использовании setjmp/longjmp следует знать, что они влияют на справедливость локальных переменных, которые часто не учитываются.
Ср my вопрос по этому вопросу.

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

Как и всякая умная теория, она разваливается, когда встречается с реальностью. Ваши промежуточные функции будут выделять память, захватывать блокировки, открывать файлы и делать всевозможные вещи, требующие очистки. Поэтому на практике setjmp / longjmp обычно плохой идеей, за исключением очень ограниченных ситуаций, когда у вас есть полный контроль над вашей средой (некоторые встроенные платформы).

В моем опыте в большинстве случаев всякий раз, когда вы думаете, что использование setjmp / longjmp будет работать, ваша программа понятна и достаточно проста, что каждый вызов промежуточной функции в цепочке вызовов может обрабатывать ошибки, или это так беспорядочно и невозможно устранить, что вы должны сделать exit , когда вы столкнулись с ошибкой.

Комбинация setjmp и longjmp — это «суперпрочность goto «. Используйте с ЭКСТРЕМАЛЬНОЙ заботой. Однако, как объясняли другие, a longjmp очень полезно выйти из неприятной ситуации с ошибкой, когда вы хотите get me back to the beginning быстро, вместо того, чтобы сбрасывать сообщение об ошибке для 18 уровней функций.

Однако, как и goto , но хуже, вы должны быть ДЕЙСТВИТЕЛЬНО осторожны, как вы это используете. A longjmp вернет вас к началу кода. Это не повлияет на все другие состояния, которые могут измениться между setjmp и вернуться к началу setjmp . Таким образом, выделения, блокировки, полуинициализированные структуры данных и т.д. По-прежнему выделяются, блокируются и частично инициализируются, когда вы возвращаетесь туда, где был вызван setjmp . Это означает, что вам действительно нужно позаботиться о местах, где вы это делаете, что ДЕЙСТВИТЕЛЬНО нормально называть longjmp , не вызывая БОЛЬШЕ проблем. Конечно, если следующая вещь, которую вы делаете, это «перезагрузка» [после сохранения сообщения об ошибке, возможно] — во встроенной системе, где вы обнаружили, что оборудование находится в плохом состоянии, например, тогда отлично.

Я также видел, что setjmp / longjmp используется для обеспечения очень простых механизмов потоков. Но этот довольно частный случай — и определенно не то, как работают «стандартные» потоки.

Изменить: можно было бы, конечно, добавить код, чтобы «справиться с очисткой», так же, как С++ хранит точки исключения в скомпилированном коде, а затем знает, что дало исключение и что нужно очистить. Это будет связано с какой-то таблицей указателей функций и сохранением «если мы выпрыгнем снизу сюда, вызовите эту функцию с помощью этого аргумента». Что-то вроде этого:

С помощью этой системы вы можете выполнить «полную обработку исключений, такую ​​как С++». Но это довольно грязно и полагается на хорошо написанный код.


Изменение и настройка переходов

На этой странице

Отображение переходов на панели «Элементы управления эффектами»

Можно использовать панель «Элементы управления эффектами», чтобы изменить параметры перехода, установленного в эпизоде. Параметры в каждом переходе различаются. На панели «Элементы управления эффектами» соседние клипы и переходы отображаются в формате A-roll/B-roll.

  • Чтобы открыть переход панели «Элементы управления эффектами», щелкните переход на панели «Таймлайн».
  • Чтобы отобразить или скрыть линейку времени на панели «Элементы управления эффектами», нажмите кнопку «Показать/Скрыть таймлайн» . При необходимости разверните панель, чтобы сделать эта кнопка стала видимой и активной.
  • Чтобы воспроизвести переход на панели «Элементы управления эффектами», кнопку «Воспроизвести переход». Это не влияет на программный монитор.
  • Чтобы просмотреть кадры из самого клипа или клипов на панели «Элементы управления эффектами», нажмите «Показать фактические источники».
  • Для просмотра нужного кадра перехода в сжатом предпросмотре нажмите кнопку «Воспроизвести переход». Затем перетащите индикатор текущего времени на линейке времени панели «Элементы управления эффектами» на нужный кадр.

Ключевые кадры нельзя использовать с переходами. Для переходов «Просмотр таймлайна» на панели «Элементы управления эффектами» используется для настройки выравнивания и длительности перехода.

Kevin Monahan рассказывает, как создавать новые переходы на основе эффектов в своей статье на сайте Adobe: использование эффектов в качестве переходов в Adobe Premiere Pro в своем блоге.

Настройка выравнивания перехода

Можно изменить выравнивание перехода, помещенного между двумя клипами либо в панели «Таймлайн», либо в панели «Элементы управления эффектами». Переход не должны быть размещены по центру или строго привязаны к стыку. Можно перетащить переход, чтобы изменить его положение на стыке по своему усмотрению.

Нельзя превратить двусторонний переход в односторонний переход. Если вы перестраиваете двусторонний переход в начало или конец клипа, он будет использовать маркеры из соседнего клипа.

Выравнивание перехода на панели «Таймлайн»

Выравнивание перехода с помощью панели «Элементы управления эффектами»

Для центровки перехода по линии стыка на панели «Элементы управления эффектами» дважды щелкните переход и выберите «Центр на стыке».

Чтобы поместить весь переход в клип, предшествующий точке редактирования, перетащите переход влево, чтобы выровнять его окончание с точкой редактирования. Кроме того, на панели «Элементы управления эффектами», дважды щелкните переход и выберите «Окончание на стыке».

Чтобы поместить весь переход в клип, следующий за точкой редактирования, перетащите переход вправо, чтобы выровнять его начало с точкой редактирования. Кроме того, на панели «Элементы управления эффектами», дважды щелкните переход и выберите «Начало на стыке».

Чтобы поместить неравные части перехода в каждый клип, перетащите переход немного влево или вправо. Для более точного управления, увеличьте линейку времени.

Можно также выбрать параметр в меню «Выравнивание» на панели «Элементы управления эффектами». Пользовательское начало отображается в качестве параметра в поле «Выравнивание» только при перетаскивании перехода к пользовательской точке на стыке.

Перемещение стыка вместе с переходом

Положение стыка можно изменить на панели «Элементы управления эффектами». Перемещение линии стыка изменяет точки входа и выхода клипов, но не влияет на длительность фильма. При перемещении стыка переход перемещается с ним.

Вы не можете переместить стык за пределы окончания клипа. Если ни в одном из клипов нет обрезанных кадров, выходящих за границу стыка, переместить стык нельзя.

Setjmp установить точку длинного перехода

Функция longjmp() возобновляет выполнение программы с места последнего обращения к функции setjmp(). Таким образом, функции longjmp() и setjmp() предоставляют средство передачи управления между функциями. Обратите внимание на необходимость включения заголовка (в языке C++ используется заголовок ).

Функция longjmp() восстанавливает состояние стека, сохраненное в буфере envbuf с помощью функции setjmp(). В результате выполнение программы возобновляется с оператора, следующего за вызовом функции setjmp(). Иначе говоря, компьютер вводится в «заблуждение»: он считает, будто управление программой не выходило за пределы функции, которая вызвала функцию setjmp().(Выражаясь образно, функция longjmp() «искривляет» время и пространство(памяти), чтобы вернуться в предыдущую точку программы, не выполняя нормальный процесс возврата из функции.)

Буфер evnbuf имеет тип jmp_buf, который определен в заголовке . Этот буфер должен быть установлен посредством обращения к функции setjmp() до вызова функции longjmp().

Значение параметра status становится возвращаемым значением функции setjmp(), и его можно опросить, чтобы определить «происхождение» длинного перехода. Единственным недопустимым значением является нуль. Функция setjmp() возвращает нуль в том случае, когда она вызывается непосредственно программой, а не косвенно, путем выполнения функции longjmp().

В основном, функция longjmp() используется для возврата из глубоко вложенного набора функций при возникновении ошибок.

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL