Если техническая статья является только теоретическим хайпом, а сама по себе ничего придумать не может, то как бы хорошо она ни была написана, ее можно расценивать только как бумажку. Вслед за предыдущей статьей«Погружение во внутренности трубопроводов Shell»Получив множество лайков от читателей и фанатов, в этой статье мы реализуем функцию персонажа-трубы. Например, мы будем поддерживать следующие сложные инструкции, серию инструкций с множеством сокетов.
$ cmd1 | cmd2 | cmd3 | cmd4 | cmd5
Мы собираемся использовать язык Python, потому что ни Go, ни Java не поддерживают функцию fork. В конечном итоге нам понадобится следующая картина.Эта картинка очень проста, но для ее построения требуется много усилий.
Кодовое имя файла программыpipe.py, программа работает следующим образом
python pipe.py "cat pipe.py | grep def | wc -l"
статистикаpipe.pyКоличество определяющих слов, содержащихся в коде файла, вывод
3
выполнение инструкции
Выполнение каждой инструкции должно нести по крайней мере один канал, либо левый канал, либо правый канал. Первая и последняя инструкции имеют только одну вертикальную черту, а средние инструкции имеют две вертикальные черты. Канал идентифицируется своей парой дескрипторов чтения и записи (r, w).
Дескриптор чтения left_pipe[0] левого канала является стандартным вводом процесса стыковки. Дескриптор записи right_pipe[1] правого канала соединяется со стандартным выходом процесса. После настройки дескриптора можно использовать функцию exec для выполнения инструкции.
def run_cmd(cmd, left_pipe, right_pipe):
if left_pipe:
os.dup2(left_pipe[0], sys.stdin.fileno())
os.close(left_pipe[0])
os.close(left_pipe[1])
if right_pipe:
os.dup2(right_pipe[1], sys.stdout.fileno())
os.close(right_pipe[0])
os.close(right_pipe[1])
# 分割指令参数
args = [arg.strip() for arg in cmd.split()]
args = [arg for arg in args if arg]
try:
# 传入指令名称、指令参数数组
# 指令参数数组的第一个参数就是指令名称
os.execvp(args[0], args)
except OSError as ex:
print "exec error:", ex
отношение процесса
Если оболочке необходимо запустить несколько процессов, она должна использовать функцию fork для создания подпроцесса, а затем использовать подпроцесс для выполнения инструкций.
Сыновья рождают внуков, а внуки рождают сыновей, а потомки детей бесконечны. Теоретически большое количество входных и выходных потоков процесса можно соединить последовательно с помощью каналов.# 指令的列表以及下一条指令左边的管道作为参数
def run_cmds(cmds, left_pipe):
# 取出指令串的第一个指令,即将执行这第一个指令
cur_cmd = cmds[0]
other_cmds = cmds[1:]
# 创建管道
pipe_fds = ()
if other_cmds:
pipe_fds = os.pipe()
# 创建子进程
pid = os.fork()
if pid < 0:
print "fork process failed"
return
if pid > 0:
# 父进程来执行指令
# 同时传入左边和右边的管道(可能为空)
run_cmd(cur_cmd, left_pipe, pipe_fds)
elif other_cmds:
# 莫忘记关闭不再使用的描述符
if left_pipe:
os.close(left_pipe[0])
os.close(left_pipe[1])
# 子进程递归继续执行后续指令,携带新创建的管道
run_cmds(other_cmds, pipe_fds)
сценарий запуска
Необходимо разделить параметры командной строки вертикальными линиями, чтобы получить несколько инструкций, и запустить рекурсивное выполнение.
def main(cmdtext):
cmds = [cmd.strip() for cmd in cmdtext.split("|")]
# 第一条指令左边没有管道
run_cmds(cmds, ())
if __name__ == '__main__':
main(argv[1])
Соблюдайте отношения процесса
Поскольку время выполнения нескольких инструкций в примере слишком мало, невозможно наблюдать взаимосвязь процесса с помощью команды ps. Поэтому мы добавили код вывода для отладки в код для вывода имени инструкции, номера процесса и номера родительского процесса, выполняемого текущим процессом.
def run_cmd(cmd, left_pipe, right_pipe):
print cmd, os.getpid(), os.getppid()
...
Наблюдайте за выводом при запуске скрипта
$ python pipe.py "cat pipe.py | grep def | wc -l"
cat pipe.py 49782 4503
grep def 49783 49782
wc -l 49784 49783
3
Взаимосвязь между родительским и дочерним процессами хорошо видна из выходных данных, N-й процесс инструкций является родительским процессом N+1-го процесса инструкций. В функции run_cmds после разветвления дочернего процесса родительский процесс отвечает за выполнение текущей инструкции, а остальные инструкции передаются дочернему процессу для выполнения. Таким образом, формируется вышеуказанная зависимость процесса. Читатель может попытаться настроить интерактивный порядок выполнения, позволить дочернему процессу нести ответственность за выполнение текущей инструкции, а затем наблюдать за выводом.
$ python pipe.py "cat pipe.py | grep def | wc -l"
cat pipe.py 49949 49948
grep def 49950 49948
wc -l 49951 49948
3
Вы обнаружите, что все эти три процесса команд имеют один и тот же родительский процесс, которым является процесс Python. Как показано на рисунке выше, отношения процессов, формируемые оболочками, которые мы обычно используем при выполнении инструкций, имеют именно такую форму, и эта форма выглядит более понятной с точки зрения логической структуры.
Если вам нужен полный исходный код, указанный выше, обратите внимание на следующую общедоступную учетную запись и ответьте на «pipe» в ней, чтобы получить исходный код.
Прочтите более подробные технические статьи, отсканируйте приведенный выше QR-код и подпишитесь на общедоступную учетную запись WeChat «Code Cave».