Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

สร้างเครื่องมือ Linux ของคุณเองด้วย Ruby: คู่มือปฏิบัติ

มาสร้างโปรเจกต์ร่วมกันกันเถอะ!

เครื่องมือ Linux เช่น ps, top และ netstat เป็นเครื่องมือที่ยอดเยี่ยม พวกมันให้ข้อมูลมากมายเกี่ยวกับสิ่งที่กำลังเกิดขึ้นในระบบของคุณ

  • แต่มันทำงานอย่างไร?
  • มันดึงข้อมูลมาจากไหน?
  • เราสามารถใช้ข้อมูลเหล่านี้สร้างเครื่องมือของตัวเองได้อย่างไร?

ในบทความนี้ เราจะสร้างเครื่องมือ Linux 3 ตัวที่นิยมใช้ร่วมกัน คุณจะได้เรียนรู้ 2 เรื่องใน 1 ครั้ง: ทั้งเทคนิค Ruby และความรู้ Linux ที่มีประโยชน์พร้อมกัน! 🙂

ค้นหาข้อมูลสถานะ

ลองตอบคำถามว่าเครื่องมือเหล่านี้หาข้อมูลจากที่ไหน คำตอบอยู่ที่ proc filesystem!

ถ้าคุณดูภายในไดเรกทอรี /proc จะเห็นเป็นชุดของไดเรกทอรีและไฟล์ คล้ายกับไดเรกทอรีอื่นๆ บนคอมพิวเตอร์ของคุณ แต่นี่ไม่ใช่ไฟล์จริงๆ มันเป็นเพียงวิธีที่ Linux kernel ใช้เปิดเผยข้อมูลให้ผู้ใช้ ซึ่งสะดวกมากเพราะสามารถอ่านได้เหมือนไฟล์ทั่วไปโดยไม่ต้องใช้เครื่องมือพิเศษ ในโลก Linux หลายอย่างทำงานแบบนี้ เช่นลองดูที่ /dev directory

ตอนนี้เราเข้าใจแล้ว มาดูเนื้อหาของ /proc directory...

1 10 104 105 11 11015 11469 11474 11552 11655

นี่เป็นเพียงตัวอย่างเล็กน้อย แต่คุณจะสังเกตรูปแบบได้เร็ว ตัวเลขเหล่านี้คืออะไร?

ได้คำตอบแล้ว: นี่คือ PIDs (Process IDs) ทุก entry มีข้อมูลเกี่ยวกับ process เฉพาะเจาะจง ถ้ารัน ps จะเห็นว่าแต่ละ process มี PID ที่เชื่อมโยง:

PID TTY TIME CMD
15952 pts/5 00:00:00 ps
22698 pts/5 00:00:01 bash

จากนี้เราสรุปได้ว่า ps ทำงานโดยวนลูปผ่าน /proc directory และพิมพ์ข้อมูลที่พบ มาดูว่ามีอะไรอยู่ในหนึ่งในไดเรกทอรีที่มีตัวเลข:

attr autogroup auxv cgroup clear_refs cmdline comm cpuset cwd environ exe fd

นี่เป็นตัวอย่างบางส่วน เพื่อประหยัดพื้นที่ แต่ขอแนะนำให้ดูรายการเต็ม นี่คือ entry ที่น่าสนใจ:

Entry คำอธิบาย
comm ชื่อของโปรแกรม
cmdline คำสั่งที่ใช้เปิด process นี้
environ ตัวแปรสภาพแวดล้อมที่ process เริ่มต้นด้วย
status สถานะ process (running, sleeping...) และการใช้หน่วยความจำ
fd ไดเรกทอรีที่มี file descriptors (ไฟล์เปิด, sockets...)

ตอนนี้เรารู้แล้ว ลองเริ่มเขียนเครื่องมือกัน!

วิธีแสดงรายการโปรแกรมที่กำลังทำงาน

เริ่มจากการดึงรายการไดเรกทอรีทั้งหมดภายใต้ /proc โดยใช้ class Dir:

Dir.glob("/proc/[0-9]*")

สังเกตว่าฉันใช้ช่วงตัวเลข เพราะมีไฟล์อื่นๆ ใน /proc ที่เราไม่สนใจ ตอนนี้เราต้องการเฉพาะไดเรกทอรีที่เป็นตัวเลข ต่อมาเราสามารถวนลูปและพิมพ์ 2 คอลัมน์: PID และชื่อโปรแกรม

pids = Dir.glob("/proc/[0-9]*")
puts "PID\tCMD"
puts "-" * 15
pids.each do |pid|
  cmd = File.read(pid + "/comm")
  pid = pid.scan(/\d+/).first
  puts "#{pid}\t#{cmd}"
end

ผลลัพธ์:

PID CMD
---------------
1 systemd
2 kthreadd
3 ksoftirqd/0
5 kworker/0
7 migration/0
8 rcu_preempt
9 rcu_bh
10 rcu_sched

เฮ้ย! เราเพิ่งสร้าง ps ขึ้นมา แม้จะไม่รองรับ options ทั้งหมดของต้นฉบับ แต่เราสร้างอะไรบางอย่างที่ทำงานได้แล้ว!

ใครกำลัง Listen อยู่?

ลองเลียนแบบ netstat ซึ่ง output แบบนี้ (ใช้ flags -ant):

Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:5432 0.0.0.0:* LISTEN
tcp 0 0 192.168.1.82:39530 182.14.172.159:22 ESTABLISHED

ข้อมูลนี้อยู่ที่ไหน? ถ้าคุณตอบ /proc ถูกต้อง! อย่างเฉพาะเจาะจงอยู่ที่ /proc/net/tcp แต่มีปัญหาเล็กน้อย: มันไม่ได้ดูเหมือน output ของ netstat เลย!

0: 0100007F:1538 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1001 0 9216
1: 2E58A8C0:9A6A 9FBB0EB9:0016 01 00000000:00000000 00:00000000 00000000 1000 0 258603

หมายความว่าเราต้องทำ parsing ด้วย regular expressions ตอนนี้มาจัดการแค่ local address และ status ก่อน

Regex ที่ผมสร้าง:

LINE_REGEX = /\s+\d+: (?<local_addr>\w+):(?<local_port>\w+) \w+:\w+ (?<status>\w+)/

จะได้ค่า hexadecimal ที่ต้องแปลงเป็น decimal มาสร้าง class เพื่อจัดการ:

class TCPInfo
  def initialize(line)
    @data = parse(line)
  end

  def parse(line)
    line.match(LINE_REGEX)
  end

  def local_port
    @data["local_port"].to_i(16)
  end

  # แปลง hex เป็น IP notation ปกติ
  def local_addr
    decimal_to_ip(@data["local_addr"].to_i(16))
  end

  STATUSES = {
    "0A" => "LISTENING",
    "01" => "ESTABLISHED",
    "06" => "TIME_WAIT",
    "08" => "CLOSE_WAIT"
  }

  def status
    code = @data["status"]
    STATUSES.fetch(code, "UNKNOWN")
  end

  # อาจารย์คณิตศาสตร์ไบนารี - อย่ากังวลมาก
  def decimal_to_ip(decimal)
    ip = []
    ip << (decimal >> 24 & 0xFF)
    ip << (decimal >> 16 & 0xFF)
    ip << (decimal >> 8 & 0xFF)
    ip << (decimal & 0xFF)
    ip.join(".")
  end
end

สิ่งที่เหลือคือพิมพ์ผลลัพธ์ในรูปแบบตารางที่สวยงาม:

require 'table_print'
tp connections

ตัวอย่าง output:

STATUS | LOCAL_PORT | LOCAL_ADDR
------------|------------|--------------
LISTENING | 5432 | 127.0.0.1
ESTABLISHED | 39530 | 192.168.88.46

Gem นี้เก๋จริงๆ! เพิ่งรู้จัก และดูเหมือนจะไม่ต้องมานั่งลุยกับ ljust / rjust อีกต่อไป 🙂

หยุดใช้ Port ของผม!

เคยเห็นข้อความนี้ไหม?

Address already in use - bind(2) for "localhost" port 5000

อึม... ตัวไหนใช้ port นี้อยู่? มาหาลองดู:

fuser -n tcp -v 5000
PORT USER PID ACCESS CMD
5000/tcp rubyguides 30893 F.... nc

เจอแล้ว! ตอนนี้เราสามารถหยุดโปรแกรมนั้นได้ถ้าไม่ต้องการให้มันรันต่อ ซึ่งจะปลด port ออกมา โปรแกรม fuser หาได้อย่างไร? คุณเดาถูก! /proc filesystem อีกครั้ง มันรวม 2 เรื่องที่เราเรียนมา: การเดินผ่าน process list และการอ่าน active connections จาก /proc/net/tcp

เราต้องการขั้นตอนเพิ่มเติมอีก 1 ขั้น: หาวิธี match ข้อมูล open port กับ PID จากข้อมูล TCP ใน /proc/net/tcp ไม่มี PID แต่เราสามารถใช้ inode number ได้

"Inode คือโครงสร้างข้อมูลที่ใช้แทน filesystem object" — Wikipedia

เราจะใช้ inode หา process ที่ตรงกันอย่างไร? ถ้าดูที่ fd directory ของ process ที่รู้ว่ามี open port จะเจอบรรทัดแบบนี้:

/proc/3295/fd/5 -> socket:[12345]

ตัวเลขในวงเล็บคือ inode number ตอนนี้แค่วนลูปหาไฟล์ทั้งหมด จะเจอ process ที่ตรงกัน

วิธีทำหนึ่งแบบ:

x = Dir.glob("/proc/[0-9]*/fd/*").find do |fd|
  File.readlink(fd).include? "socket:[#{socket_inode}]"
rescue nil
end
pid = x.scan(/\d+/).first
name = File.readlink("/proc/#{pid}/exe")
puts "Port #{hex_port.to_i(16)} in use by #{name} (#{pid})"

ตัวอย่าง output:

Port 5432 in use by /usr/bin/postgres (474)

หมายเหตุ: คุณต้องรันโค้ดนี้เป็น root หรือเป็นเจ้าของ process มิฉะนั้นจะอ่านรายละเอียด process ใน /proc ไม่ได้

สรุป

ในบทความนี้คุณเรียนรู้ว่า Linux เปิดเผยข้อมูลมากมายผ่าน virtual /proc filesystem และเรียนรู้วิธีสร้างเครื่องมือ Linux ยอดนิยมอย่าง ps, netstat และ fuser โดยใช้ข้อมูลภายใต้ /proc

อย่าลืมสมัครรับ newsletter ด้านล่างเพื่อไม่พลาดบทความถัดไป! 🙂