Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Tự Tạo Công Cụ Linux Với Ruby: Hướng Dẫn Thực Hành

Hãy cùng xây dựng một dự án nhé!

Các công cụ Linux như ps, top và netstat rất hữu ích. Chúng cung cấp nhiều thông tin về tình trạng hệ thống.

  • Nhưng chúng hoạt động như thế nào?
  • Chúng lấy thông tin từ đâu?
  • Làm thế nào để tận dụng điều này xây dựng công cụ riêng?

Trong bài viết này, chúng ta sẽ cùng tái tạo ba công cụ Linux phổ biến.

Bạn sẽ thu được "hai trong một": học các thủ thuật Ruby và kiến thức Linux hữu ích cùng lúc! 🙂

Tìm Thông Tin Trạng Thái

Hãy tìm hiểu xem các công cụ này lấy thông tin từ đâu.

Câu trả lời nằm trong hệ thống tệp proc (/proc)!

Nếu bạn xem bên trong thư mục /proc, nó trông như một đống thư mục và tệp tin, giống như bất kỳ thư mục nào khác trên máy tính.

Đây không phải là các tệp tin thật, mà chỉ là cách nhân Linux cung cấp dữ liệu cho người dùng.

Rất tiện lợi vì chúng có thể được xử lý như tệp tin bình thường, nghĩa là bạn có thể đọc chúng mà không cần công cụ đặc biệt.

Trong thế giới Linux, nhiều thứ hoạt động theo cách này. Nếu muốn xem ví dụ khác, hãy nhìn vào thư mục /dev.

Bây giờ đã hiểu bản chất, hãy xem nội dung thư mục /proc…

1 10 104 105 11 11015 11469 11474 11552 11655

Đây chỉ là mẫu nhỏ, nhưng bạn nhanh chóng nhận ra quy luật.

Những con số này là gì?

Chúng chính là PID (Process ID - ID tiến trình).

Mỗi mục chứa thông tin về một tiến trình cụ thể.

Nếu chạy ps, bạn sẽ thấy mỗi tiến trình có một PID:

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

Từ đây suy ra ps chỉ lặp qua thư mục /proc và in thông tin tìm được.

Hãy xem bên trong một thư mục có số:

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

Chỉ là mẫu để tiết kiệm không gian, khuyến khích bạn tự xem danh sách đầy đủ.

Một số mục thú vị:

MụcMô tả
commTên chương trình
cmdlineLệnh dùng để khởi chạy tiến trình
environBiến môi trường khi tiến trình khởi động
statusTrạng thái tiến trình (running, sleeping…) và sử dụng bộ nhớ
fdThư mục chứa file descriptor (tệp mở, socket…)

Với kiến thức này, chúng ta có thể bắt đầu viết công cụ!

Liệt Kê Chương Trình Đang Chạy

Bắt đầu bằng cách lấy danh sách tất cả thư mục dưới /proc.

Sử dụng lớp Dir:

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

Lưu ý dùng dải số vì dưới /proc còn có tệp khác không quan tâm, chỉ cần thư mục có số.

Bây giờ lặp qua danh sách và in hai cột: PID và tên chương trình.

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

Kết quả đầu ra:

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

Hey, chúng ta vừa tạo ra ps! Dù chưa hỗ trợ tất cả tùy chọn phức tạp, nhưng đã có công cụ hoạt động.

Ai Đang Lắng Nghe?

Thử tái tạo netstat, đầu ra mẫu (với cờ -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

Thông tin này ở đâu? Nếu bạn nói "bên trong /proc" thì đúng! Cụ thể tại /proc/net/tcp.

Nhưng có vấn đề: định dạng này hoàn toàn khác đầu ra 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

Cần phân tích cú pháp bằng biểu thức chính quy. Hiện tại chỉ quan tâm địa chỉ cục bộ và trạng thái.

Regex sử dụng:

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

Cho giá trị hexadecimal, cần chuyển sang thập phân. Tạo lớp xử lý:

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

  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

Chỉ còn in kết quả dưới dạng bảng đẹp.

require 'table_print'
tp connections

Ví dụ đầu ra:

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

Gem này tuyệt vời! Vừa biết đến nó và không phải loay hoay với ljust/rjust nữa 🙂

Đừng Dùng Cổng Của Tôi!

Bạn từng thấy thông báo này?

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

Uhm… Chương trình nào đang dùng cổng đó?

Hãy tìm ra:

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

À, tìm thấy thủ phạm! Có thể dừng chương trình đó để giải phóng cổng. fuser tìm ra như thế nào?

Bạn đoán đúng! Lại là hệ thống tệp /proc.

Thực tế nó kết hợp hai điều đã biết: duyệt danh sách tiến trình và đọc kết nối hoạt động từ /proc/net/tcp.

Chỉ cần thêm một bước:

Tìm cách khớp thông tin cổng mở với PID.

Dữ liệu TCP từ /proc/net/tcp không chứa PID. Nhưng có thể dùng số inode.

"Inode là cấu trúc dữ liệu đại diện cho đối tượng hệ thống tệp." – Wikipedia

Làm thế nào dùng inode tìm tiến trình khớp? Nếu xem thư mục fd của tiến trình có cổng mở, sẽ thấy dòng như:

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

Số trong ngoặc vuông là inode. Chỉ cần lặp qua tất cả tệp để tìm tiến trình khớp.

Cách thực hiện:

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})"

Ví dụ đầu ra:

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

Lưu ý: Cần chạy code với quyền root hoặc là chủ sở hữu tiến trình. Nếu không, không thể đọc chi tiết tiến trình trong /proc.

Kết Luận

Bài viết này bạn đã học Linux cung cấp nhiều dữ liệu qua hệ thống tệp ảo /proc. Cũng học cách tái tạo công cụ phổ biến như ps, netstat và fuser bằng dữ liệu dưới /proc.

Đừng quên đăng ký bản tin bên dưới để không bỏ lỡ bài tiếp theo. 🙂