Hạn chế sử dụng bộ nhớ cho một quy trình Linux


152

Tôi đang chạy pdftoppmđể chuyển đổi PDF do người dùng cung cấp thành hình ảnh 300DPI. Điều này hoạt động rất tốt, trừ khi người dùng cung cấp một tệp PDF với kích thước trang rất lớn. pdftoppmsẽ phân bổ đủ bộ nhớ để chứa hình ảnh 300DPI có kích thước đó trong bộ nhớ, mà đối với trang vuông 100 inch là 100 * 300 * 100 * 300 * 4 byte mỗi pixel = 3,5GB. Một người dùng độc hại có thể chỉ cho tôi một bản PDF lớn ngớ ngẩn và gây ra tất cả các loại vấn đề.

Vì vậy, những gì tôi muốn làm là đặt một số giới hạn cứng cho việc sử dụng bộ nhớ cho một tiến trình con tôi sắp chạy - chỉ cần xử lý chết nếu nó cố phân bổ nhiều hơn 500 MB bộ nhớ. Điều đó có thể không?

Tôi không nghĩ rằng ulimit có thể được sử dụng cho việc này, nhưng liệu có tương đương một quá trình không?


Có lẽ dockernào?
Sridhar Sarnobat

Câu trả lời:


58

Có một số vấn đề với ulimit. Đây là một bài đọc hữu ích về chủ đề: Hạn chế thời gian và mức tiêu thụ bộ nhớ của một chương trình trong Linux , dẫn đến công cụ hết thời gian , cho phép bạn lồng một tiến trình (và các nhánh của nó) theo thời gian hoặc mức tiêu thụ bộ nhớ.

Công cụ hết thời gian yêu cầu Perl 5+ và /prochệ thống tập tin được gắn kết. Sau đó, bạn sao chép công cụ để ví dụ /usr/local/binnhư vậy:

curl https://raw.githubusercontent.com/pshved/timeout/master/timeout | \
  sudo tee /usr/local/bin/timeout && sudo chmod 755 /usr/local/bin/timeout

Sau đó, bạn có thể 'lồng' quy trình của mình bằng mức tiêu thụ bộ nhớ như trong câu hỏi của bạn như vậy:

timeout -m 500 pdftoppm Sample.pdf

Ngoài ra, bạn có thể sử dụng -t <seconds>-x <hertz>để tương ứng giới hạn quá trình theo thời gian hoặc các ràng buộc CPU.

Cách thức hoạt động của công cụ này là bằng cách kiểm tra nhiều lần trong một giây nếu quá trình sinh ra chưa đăng ký vượt quá các ranh giới đã đặt của nó. Điều này có nghĩa là thực sự có một cửa sổ nhỏ nơi một quy trình có khả năng được đăng ký vượt mức trước khi thông báo hết thời gian và giết chết quy trình.

Do đó, một cách tiếp cận đúng hơn có thể liên quan đến các nhóm, nhưng nó liên quan nhiều hơn đến việc thiết lập, ngay cả khi bạn sử dụng Docker hoặc runC, trong số đó, cung cấp một sự trừu tượng thân thiện hơn với các nhóm.


Có vẻ như bây giờ tôi đang làm việc cho tôi (một lần nữa?) Nhưng đây là phiên bản bộ nhớ cache của google: webcache.googleusercontent.com/ Kẻ
kvz

Chúng ta có thể sử dụng thời gian chờ cùng với tasket (chúng ta cần giới hạn cả bộ nhớ và lõi) không?
chuộc

7
Cần lưu ý rằng câu trả lời này không đề cập đến coreutilstiện ích tiêu chuẩn linux cùng tên! Do đó, câu trả lời có khả năng gây nguy hiểm nếu bất cứ nơi nào trên hệ thống của bạn, một số gói có tập lệnh mong đợi timeoutcoreutilsgói chuẩn linux ! Tôi không biết công cụ này được đóng gói cho các bản phân phối như debian.
user1404316

-t <seconds>ràng buộc giết quá trình sau nhiều giây không?
xxx374562

116

Một cách khác để hạn chế điều này là sử dụng các nhóm kiểm soát của Linux. Điều này đặc biệt hữu ích nếu bạn muốn giới hạn phân bổ bộ nhớ vật lý của một quá trình (hoặc nhóm quy trình) khác biệt với bộ nhớ ảo. Ví dụ:

cgcreate -g memory:myGroup
echo 500M > /sys/fs/cgroup/memory/myGroup/memory.limit_in_bytes
echo 5G > /sys/fs/cgroup/memory/myGroup/memory.memsw.limit_in_bytes

sẽ tạo một nhóm điều khiển có tên myGroup, giới hạn tập hợp các quy trình chạy trong mygroup lên tới 500 MB bộ nhớ vật lý và tối đa 5000 MB trao đổi. Để chạy một quy trình trong nhóm kiểm soát:

cgexec -g memory:myGroup pdftoppm

Lưu ý rằng trên bản phân phối Ubuntu hiện đại, ví dụ này yêu cầu cài đặt cgroup-bingói và chỉnh sửa /etc/default/grubđể thay đổi GRUB_CMDLINE_LINUX_DEFAULTthành:

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

và sau đó chạy sudo update-grubvà khởi động lại để khởi động với các tham số khởi động kernel mới.


3
Các firejailchương trình cũng sẽ cho phép bạn bắt đầu một quá trình với giới hạn bộ nhớ (sử dụng cgroups và không gian tên để hạn chế nhiều hơn chỉ bộ nhớ). Trên hệ thống của tôi, tôi không phải thay đổi dòng lệnh kernel để nó hoạt động!
Ned64

1
Bạn có cần GRUB_CMDLINE_LINUX_DEFAULTsửa đổi để làm cho các thiết lập liên tục? Tôi tìm thấy một cách khác để làm cho nó bền bỉ ở đây .
stason


Sẽ rất hữu ích khi lưu ý trong câu trả lời này rằng trên một số bản phân phối (ví dụ: Ubuntu) sudo là bắt buộc đối với cgcreate, và cả các lệnh sau trừ khi được cấp cho người dùng hiện tại. Điều này sẽ giúp người đọc không phải tìm thông tin này ở nơi khác (ví dụ: Askubfox.com/questions 4325055 ). Tôi đề nghị chỉnh sửa hiệu ứng này nhưng nó đã bị từ chối.
stewbasic

77

Nếu quy trình của bạn không sinh ra nhiều trẻ tiêu tốn nhiều bộ nhớ nhất, bạn có thể sử dụng setrlimitchức năng. Giao diện người dùng phổ biến hơn cho việc đó là sử dụng ulimitlệnh của trình bao:

$ ulimit -Sv 500000     # Set ~500 mb limit
$ pdftoppm ...

Điều này sẽ chỉ giới hạn bộ nhớ "ảo" trong quy trình của bạn, có tính đến tài khoản và giới hạn bộ nhớ mà quy trình đang được gọi chia sẻ với các quy trình khác và bộ nhớ được ánh xạ nhưng không được bảo lưu (ví dụ, đống lớn của Java). Tuy nhiên, bộ nhớ ảo là xấp xỉ gần nhất cho các quá trình phát triển thực sự lớn, làm cho các lỗi đã nói không đáng kể.

Nếu chương trình của bạn sinh ra trẻ em và chính chúng sẽ phân bổ bộ nhớ, nó sẽ trở nên phức tạp hơn và bạn nên viết các tập lệnh phụ trợ để chạy các quy trình dưới sự kiểm soát của bạn. Tôi đã viết trong blog của mình, tại saonhư thế nào .


2
Tại sao setrlimitphức tạp hơn cho nhiều trẻ em? man setrlimitnói với tôi rằng "Một tiến trình con được tạo thông qua fork (2) thừa hưởng các giới hạn tài nguyên của cha mẹ. Các giới hạn tài nguyên được bảo toàn trong suốt quá trình thực thi (2)"
akira

6
Bởi vì kernel không tổng kích thước vm cho tất cả các tiến trình con; nếu nó đã làm nó sẽ nhận được câu trả lời sai. Giới hạn là trên mỗi quy trình và là không gian địa chỉ ảo, không sử dụng bộ nhớ. Sử dụng bộ nhớ là khó khăn hơn để đo lường.
MarkR

1
nếu tôi hiểu chính xác câu hỏi thì OP giới hạn cho mỗi quy trình con (con) .. không phải trong tổng số.
akira

@MarkR, dù sao, không gian địa chỉ ảo là một xấp xỉ tốt cho bộ nhớ được sử dụng, đặc biệt nếu bạn chạy một chương trình không được điều khiển bởi máy ảo (giả sử, Java). Ít nhất tôi không biết bất kỳ số liệu tốt hơn.

2
Chỉ muốn nói lời cảm ơn - đây ulimitcách tiếp cận giúp tôi với firefox's lỗi 622.816 - Đang tải một hình ảnh lớn có thể 'đóng băng' firefox, hoặc sụp đổ hệ thống ; mà trên một khởi động USB (từ RAM) có xu hướng đóng băng HĐH, yêu cầu khởi động lại cứng; bây giờ ít nhất là firefoxsự cố chính nó, làm cho hệ điều hành còn sống ... Chúc mừng!
sdaau

8

Tôi đang sử dụng đoạn script dưới đây, hoạt động rất tốt. Nó sử dụng các nhóm thông qua cgmanager. Cập nhật: bây giờ sử dụng các lệnh từ cgroup-tools. Đặt tên cho tập lệnh này limitmemvà đặt nó vào $ PATH của bạn và bạn có thể sử dụng nó như thế nào limitmem 100M bash. Điều này sẽ giới hạn cả bộ nhớ và sử dụng trao đổi. Để giới hạn chỉ bộ nhớ loại bỏ dòng với memory.memsw.limit_in_bytes.

chỉnh sửa: Trên các cài đặt Linux mặc định, điều này chỉ giới hạn việc sử dụng bộ nhớ, không trao đổi sử dụng. Để kích hoạt giới hạn sử dụng trao đổi, bạn cần kích hoạt tính toán trao đổi trên hệ thống Linux của mình. Làm điều đó bằng cách thiết lập / thêm swapaccount=1vào /etc/default/grubđể nó trông giống như

GRUB_CMDLINE_LINUX="swapaccount=1"

Sau đó chạy sudo update-grubvà khởi động lại.

Tuyên bố miễn trừ trách nhiệm: Tôi sẽ không ngạc nhiên nếu cgroup-toolscũng sẽ phá vỡ trong tương lai. Giải pháp chính xác là sử dụng api của systemd để quản lý nhóm nhưng không có công cụ dòng lệnh nào cho atm đó

#!/bin/sh

# This script uses commands from the cgroup-tools package. The cgroup-tools commands access the cgroup filesystem directly which is against the (new-ish) kernel's requirement that cgroups are managed by a single entity (which usually will be systemd). Additionally there is a v2 cgroup api in development which will probably replace the existing api at some point. So expect this script to break in the future. The correct way forward would be to use systemd's apis to create the cgroups, but afaik systemd currently (feb 2018) only exposes dbus apis for which there are no command line tools yet, and I didn't feel like writing those.

# strict mode: error if commands fail or if unset variables are used
set -eu

if [ "$#" -lt 2 ]
then
    echo Usage: `basename $0` "<limit> <command>..."
    echo or: `basename $0` "<memlimit> -s <swaplimit> <command>..."
    exit 1
fi

cgname="limitmem_$$"

# parse command line args and find limits

limit="$1"
swaplimit="$limit"
shift

if [ "$1" = "-s" ]
then
    shift
    swaplimit="$1"
    shift
fi

if [ "$1" = -- ]
then
    shift
fi

if [ "$limit" = "$swaplimit" ]
then
    memsw=0
    echo "limiting memory to $limit (cgroup $cgname) for command $@" >&2
else
    memsw=1
    echo "limiting memory to $limit and total virtual memory to $swaplimit (cgroup $cgname) for command $@" >&2
fi

# create cgroup
sudo cgcreate -g "memory:$cgname"
sudo cgset -r memory.limit_in_bytes="$limit" "$cgname"
bytes_limit=`cgget -g "memory:$cgname" | grep memory.limit_in_bytes | cut -d\  -f2`

# try also limiting swap usage, but this fails if the system has no swap
if sudo cgset -r memory.memsw.limit_in_bytes="$swaplimit" "$cgname"
then
    bytes_swap_limit=`cgget -g "memory:$cgname" | grep memory.memsw.limit_in_bytes | cut -d\  -f2`
else
    echo "failed to limit swap"
    memsw=0
fi

# create a waiting sudo'd process that will delete the cgroup once we're done. This prevents the user needing to enter their password to sudo again after the main command exists, which may take longer than sudo's timeout.
tmpdir=${XDG_RUNTIME_DIR:-$TMPDIR}
tmpdir=${tmpdir:-/tmp}
fifo="$tmpdir/limitmem_$$_cgroup_closer"
mkfifo --mode=u=rw,go= "$fifo"
sudo -b sh -c "head -c1 '$fifo' >/dev/null ; cgdelete -g 'memory:$cgname'"

# spawn subshell to run in the cgroup. If the command fails we still want to remove the cgroup so unset '-e'.
set +e
(
set -e
# move subshell into cgroup
sudo cgclassify -g "memory:$cgname" --sticky `sh -c 'echo $PPID'`  # $$ returns the main shell's pid, not this subshell's.
exec "$@"
)

# grab exit code 
exitcode=$?

set -e

# show memory usage summary

peak_mem=`cgget -g "memory:$cgname" | grep memory.max_usage_in_bytes | cut -d\  -f2`
failcount=`cgget -g "memory:$cgname" | grep memory.failcnt | cut -d\  -f2`
percent=`expr "$peak_mem" / \( "$bytes_limit" / 100 \)`

echo "peak memory used: $peak_mem ($percent%); exceeded limit $failcount times" >&2

if [ "$memsw" = 1 ]
then
    peak_swap=`cgget -g "memory:$cgname" | grep memory.memsw.max_usage_in_bytes | cut -d\  -f2`
    swap_failcount=`cgget -g "memory:$cgname" |grep memory.memsw.failcnt | cut -d\  -f2`
    swap_percent=`expr "$peak_swap" / \( "$bytes_swap_limit" / 100 \)`

    echo "peak virtual memory used: $peak_swap ($swap_percent%); exceeded limit $swap_failcount times" >&2
fi

# remove cgroup by sending a byte through the pipe
echo 1 > "$fifo"
rm "$fifo"

exit $exitcode

1
call to cgmanager_create_sync failed: invalid requestcho mọi quá trình tôi cố gắng chạy với limitmem 100M processname. Tôi đang sử dụng Xubfox 16.04 LTS và gói đó đã được cài đặt.
Aaron Franke

Up, tôi nhận được thông báo lỗi này: $ limitmem 400M rstudio limiting memory to 400M (cgroup limitmem_24575) for command rstudio Error org.freedesktop.DBus.Error.InvalidArgs: invalid request có ý kiến ​​gì không?
R Kiselev

@RKiselev cgmanager hiện không được chấp nhận và thậm chí không có sẵn trong Ubuntu 17.10. Api systemd mà nó sử dụng đã bị thay đổi tại một số điểm, vì vậy đó có lẽ là lý do. Tôi đã cập nhật tập lệnh để sử dụng các lệnh cgroup-tools.
JanKanis

nếu phép tính cho percentkết quả bằng 0, exprmã trạng thái là 1 và tập lệnh này thoát sớm. khuyên bạn nên thay đổi dòng thành: percent=$(( "$peak_mem" / $(( "$bytes_limit" / 100 )) ))(ref: unix.stackexchange.com/questions/63166/NH )
Willi Ballenthin

Làm cách nào để cấu hình cgroup để giết tiến trình của tôi nếu tôi vượt quá giới hạn?
d9ngle

7

Ngoài các công cụ từ daemontools, được đề xuất bởi Mark Johnson, bạn cũng có thể xem xét chpstnhững công cụ được tìm thấy trong runit. Runit tự nó được gói trong busybox, vì vậy bạn có thể đã cài đặt nó.

Các trang người đàn ông củachpst cho thấy các tùy chọn:

-m byte bộ nhớ giới hạn. Giới hạn phân đoạn dữ liệu, phân đoạn ngăn xếp, các trang vật lý bị khóa và tổng số tất cả các phân đoạn trên mỗi quy trình cho mỗi byte byte.


3

Tôi đang chạy Ubuntu 18.04.2 LTS và tập lệnh JanKanis không hoạt động với tôi như anh ấy gợi ý. Chạy limitmem 100M scriptlà giới hạn 100 MB RAM với trao đổi không giới hạn .

Chạy limitmem 100M -s 100M scriptkhông âm thầm như cgget -g "memory:$cgname"không có tham số có tên memory.memsw.limit_in_bytes.

Vì vậy, tôi đã vô hiệu hóa trao đổi:

# create cgroup
sudo cgcreate -g "memory:$cgname"
sudo cgset -r memory.limit_in_bytes="$limit" "$cgname"
sudo cgset -r memory.swappiness=0 "$cgname"
bytes_limit=`cgget -g "memory:$cgname" | grep memory.limit_in_bytes | cut -d\  -f2`

@sourcejedi đã thêm nó :)
d9ngle

2
Phải, tôi chỉnh sửa câu trả lời của tôi. Để kích hoạt giới hạn hoán đổi, bạn cần kích hoạt kế toán hoán đổi trên hệ thống của mình. Có một thời gian chạy nhỏ để nó không được bật theo mặc định trên Ubuntu. Xem chỉnh sửa của tôi.
JanKanis

3

Trên bất kỳ bản phân phối dựa trên hệ thống nào, bạn cũng có thể sử dụng các nhóm trực tiếp thông qua hệ thống chạy. Ví dụ: đối với trường hợp giới hạn pdftoppm500M RAM, hãy sử dụng:

systemd-run --scope -p MemoryLimit=500M pdftoppm

Lưu ý: điều này sẽ yêu cầu bạn nhập mật khẩu nhưng ứng dụng sẽ được khởi chạy với tư cách là người dùng của bạn. Không cho phép điều này khiến bạn ảo tưởng rằng lệnh cần sudo, bởi vì điều đó sẽ khiến lệnh chạy dưới quyền root, điều mà hầu như không phải là ý định của bạn.

Nếu bạn muốn không nhập mật khẩu (sau tất cả, với tư cách là người dùng bạn sở hữu bộ nhớ, tại sao bạn cần mật khẩu để giới hạn mật khẩu) , bạn có thể sử dụng --usertùy chọn này, tuy nhiên để làm việc này, bạn sẽ cần hỗ trợ cgroupsv2, đúng bây giờ yêu cầu khởi động với systemd.unified_cgroup_hierarchytham số kernel .


Cảm ơn bạn, đã làm cho ngày của tôi
Geradlus_RU
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.