Computer >> Máy Tính >  >> Lập trình >> Ruby

Ý tưởng dự án Ruby:Xây dựng các công cụ Linux của riêng bạn

Hãy cùng nhau thực hiện một dự án!

Các công cụ Linux như ps , top &netstat thật tuyệt.

Họ cung cấp cho bạn nhiều thông tin về những gì đang diễn ra với hệ thống của bạn.

  • Nhưng chúng hoạt động như thế nào?
  • Họ lấy tất cả thông tin từ đâu?
  • Làm cách nào chúng ta có thể sử dụng điều này để xây dựng các công cụ của riêng mình?

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

Bạn sẽ có được bữa ăn 2 × 1, học một số thủ thuật Ruby và đồng thời nhận được kiến ​​thức hữu ích về Linux! 🙂

Tìm thông tin trạng thái

Vì vậy, hãy thử trả lời câu hỏi về nơi mà tất cả các công cụ này tìm thấy thông tin của chúng.

Câu trả lời là trong hệ thống tệp proc!

Nếu bạn nhìn vào bên trong /proc thư mục nó sẽ giống như một loạt các thư mục và tệp, giống như bất kỳ thư mục nào khác trên máy tính của bạn.

Đây không phải là các tệp thực, nó chỉ là một cách để nhân Linux hiển thị dữ liệu cho người dùng.

Điều này rất tiện lợi vì chúng có thể được coi như các tệp bình thường, có nghĩa là bạn có thể đọc chúng mà không cần bất kỳ công cụ đặc biệt nào.

Trong thế giới Linux, rất nhiều thứ hoạt động như thế này.

Nếu bạn muốn xem một ví dụ khác, hãy xem /dev thư mục.

Bây giờ chúng tôi hiểu những gì chúng tôi đang xử lý, hãy xem nội dung của /proc thư mục…

1
10
104
105
11
11015
11469
11474
11552
11655

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

Tất cả những con số đó là gì?

Chà, hóa ra đây là PID (ID quy trình).

Mỗi mục nhập đều chứa thông tin về một quy trình cụ thể.

Nếu bạn chạy ps bạn có thể thấy mọi quy trình có PID được liên kết với nó như thế nào:

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

Từ đó, chúng ta có thể suy ra rằng những gì ps làm chỉ là lặp qua /proc thư mục &in thông tin mà nó tìm thấy.

Hãy xem những gì bên trong một trong những thư mục được đánh số đó:

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

Đó chỉ là một ví dụ để tiết kiệm dung lượng, nhưng tôi khuyến khích bạn xem danh sách đầy đủ.

Đây là một số mục nhập thú vị :

Mục nhập Mô tả
comm Tên chương trình
cmdline Lệnh được sử dụng để khởi chạy quá trình này
môi trường Các biến môi trường mà quá trình này đã được bắt đầu
trạng thái Trạng thái xử lý (đang chạy, ngủ…) và sử dụng bộ nhớ
fd Thư mục chứa trình mô tả tệp (tệp đang mở, ổ cắm…)

Bây giờ chúng tôi biết điều này, chúng tôi sẽ có thể bắt đầu viết một số công cụ!

Cách liệt kê các chương trình đang chạy

Hãy bắt đầu bằng cách chỉ lấy danh sách tất cả các thư mục trong /proc .

Chúng tôi có thể làm điều này bằng cách sử dụng Dir lớp học.

Ví dụ :

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

Lưu ý cách tôi đã sử dụng một dải số, lý do là có các tệp khác trong /proc mà chúng tôi không quan tâm ngay bây giờ, chúng tôi chỉ muốn các thư mục được đánh số.

Bây giờ chúng ta có thể lặp lại danh sách này và in ra hai cột, một cột có PID và một cột khác có tên chương trình.

Ví dụ :

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

Và đây là 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

Này, có vẻ như chúng ta vừa tạo ps ! Vâng, nó không hỗ trợ tất cả các tùy chọn ưa thích từ bản gốc, nhưng chúng tôi đã làm một cái gì đó hoạt động.

Ai đang lắng nghe?

Hãy thử tái tạo netstat bây giờ, đây là kết quả trông như thế nào (với -ant dưới dạng cờ).

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

Chúng ta có thể tìm thông tin này ở đâu? Nếu bạn nói “inside /proc " bạn đúng! Để cụ thể hơn, bạn có thể tìm thấy nó trong /proc/net/tcp .

Nhưng có một vấn đề nhỏ, cái này trông không giống với netstat đầu ra!

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

Điều này có nghĩa là chúng ta cần thực hiện một số phân tích cú pháp với các biểu thức chính quy. Bây giờ, chúng ta hãy chỉ lo lắng về địa chỉ địa phương và trạng thái.

Đây là regex tôi đã nghĩ ra :

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

Điều này sẽ cung cấp cho chúng ta một số giá trị thập lục phân mà chúng ta cần chuyển đổi thành thập phân. Hãy tạo một lớp học sẽ làm điều này cho chúng tôi.

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

  # Convert hex to regular 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

  # Don't worry too much about this. It's some binary math.
  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

Việc còn lại duy nhất là in kết quả ở định dạng bảng đẹp.

require 'table_print'

tp connections

Đầu ra mẫu :

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

Vâng, viên ngọc này thật tuyệt vời!

Tôi vừa mới tìm thấy về nó và có vẻ như tôi sẽ không phải mò mẫm với ljust / rjust một lần nữa 🙂

Ngừng sử dụng cổng của tôi!

Bạn đã bao giờ thấy thông báo này chưa?

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

Ừm…

Tôi không biết chương trình nào đang sử dụng cổng đó.

Hãy cùng tìm hiểu :

fuser -n tcp -v 5000

PORT       USER        PID   ACCESS CMD
5000/tcp   rubyguides  30893 F....  nc

À, thì ra là thủ phạm của chúng ta!

Bây giờ chúng tôi có thể dừng chương trình này nếu chúng tôi không muốn nó chạy và điều đó sẽ giải phóng cổng của chúng tôi. Làm cách nào mà chương trình “fuser” tìm ra ai đang sử dụng cổng này?

Bạn đoán nó!

/proc lại hệ thống tệp.

Trên thực tế, nó kết hợp hai thứ mà chúng tôi đã đề cập:xem qua danh sách quy trình và đọc các kết nối đang hoạt động từ /proc/net/tcp .

Chúng tôi chỉ cần thêm một bước :

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

Nếu chúng ta xem dữ liệu TCP mà chúng ta có thể lấy từ /proc/net/tcp , PID không có ở đó. Nhưng chúng ta có thể sử dụng số inode.

“Một inode là một cấu trúc dữ liệu được sử dụng để đại diện cho một đối tượng hệ thống tệp.” - Wikipedia

Làm cách nào chúng ta có thể sử dụng inode để tìm quá trình đối sánh? Nếu chúng ta xem dưới fd thư mục của một tiến trình mà chúng tôi biết có một cổng đang mở, chúng tôi sẽ tìm thấy một dòng như thế này:

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

Số giữa các dấu ngoặc là số inode. Vì vậy, bây giờ tất cả những gì chúng tôi phải làm là lặp lại tất cả các tệp và chúng tôi sẽ tìm thấy quy trình phù hợp.

Đây là một cách để làm điều đó :

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)

Xin lưu ý rằng bạn sẽ cần chạy mã này với tư cách là người chủ hoặc với tư cách là chủ sở hữu quá trình.

Nếu không, bạn sẽ không thể đọc chi tiết quy trình bên trong /proc .

Kết luận

Trong bài đăng này, bạn đã biết rằng Linux hiển thị rất nhiều dữ liệu qua /proc ảo hệ thống tập tin. Bạn cũng đã học cách tạo lại các công cụ Linux phổ biến như ps, netstat &fuser bằng cách sử dụng dữ liệu trong /proc .

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