进程控制

进程终止

Linux 支持 POSIX 可靠信号(“标准信号”)和 POSIX 实时信号。在 Linux 和 Unix 操作系统中,信号不过是某种进程间通信(一种或多种进程或命令中的多个线程之间交换数据的技术)。信号被发送到进程或命令以通知发生的事件。 例如,在运行名为 ls -R / 的命令时,您可以按 CTRL + C(或 Break)以取消命令执行。按下 CTRL + C 后,就会发送一个称为 SIGINT(2)的信号来指示键盘中断。当 SIGINT 发送到 ls 命令时,Linux 中断了进程的正常执行流程。在此示例中,ls 命令被终止。

但是,您可以为 CTRL + C 注册一个信号处理程序,并采取某种措施,例如忽略它,或者在 ls 命令被 SIGINT 中断时在屏幕上显示一条消息。您需要使用 trap 命令在 Linux Shell 脚本下捕获信号并处理错误。您可以将各种信号发送到命令和过程。例如,要终止前台进程,您可以按 Ctrl + C 组合键。要终止后台进程,可以使用 kill 命令并发送 SIGTERM(终止命令):

kill

默认的终止信号是 TERM。要列出可用信号,请输入:

kill -l

1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL
 5) SIGTRAP	 6) SIGABRT	 7) SIGBUS	 8) SIGFPE
 9) SIGKILL	10) SIGUSR1	11) SIGSEGV	12) SIGUSR2
13) SIGPIPE	14) SIGALRM	15) SIGTERM	16) SIGSTKFLT
17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU
25) SIGXFSZ	26) SIGVTALRM	27) SIGPROF	28) SIGWINCH
29) SIGIO	30) SIGPWR	31) SIGSYS	34) SIGRTMIN
35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3	38) SIGRTMIN+4
39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12
47) SIGRTMIN+13	48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14
51) SIGRTMAX-13	52) SIGRTMAX-12	53) SIGRTMAX-11	54) SIGRTMAX-10
55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7	58) SIGRTMAX-6
59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

kill 命令可以将上述所有信号发送到命令和进程。但是,仅当命令被编程为识别那些信号时,它们才会给出响应。特别有用的信号包括:

  • SIGHUP (1) - Hangup detected on controlling terminal or death of controlling process.
  • SIGINT (2) - Interrupt from keyboard.
  • SIGKILL (9) - Kill signal i.e. kill running process.
  • SIGSTOP (19) - Stop process.
  • SIGCONT (18) - Continue process if stopped.
Number Constant Description Default action Trappable (Yes/No)
0 0 Success Terminate the process. Yes
1 SIGHUP Hangup detected on controlling terminal or death of controlling process. Also, used to reload configuration files for many UNIX / Linux daemons. Terminate the process. Yes
2 SIGINT Interrupt from keyboard (Ctrl+C) Terminate the process. Yes
3 SIGQUIT Quit from keyboard (Ctrl-. or, Ctrl-4 or, on the virtual console, the SysRq key) Terminate the process and dump core. Yes
4 SIGILL Terminate the process and dump core. Illegal instruction. Yes
6 SIGABRT Abort signal from abort(3) - software generated. Terminate the process and dump core. Yes
8 SIGFPE Floating point exception. Terminate the process and dump core. Yes
9 SIGKILL Kill signal Terminate the process. No
15 SIGTERM Termination signal Terminate the process. Yes
20 SIGSTP Stop typed at tty (CTRL+z) Stop the process. Yes

要将终止信号发送给 PID#1234,请使用:

$ kill -9 1234
$ kill -KILL 1234
$ kill -SIGKILL 1234

killall - kill processes by name

killall processName
killall firefox-bin`

# To send a KILL signal to firefox
killall -s SIGKILL firefox-bin

pkill - kill process

pkill 命令是另一个具有附加选项的命令,可以通过其名称,用户名,组名,终端,UID,EUID 和 GID 终止进程。它将向每个进程发送指定的信号(默认为 SIGTERM),而不是在 stdout 上列出它们。要将终止信号发送到 php-cgi 进程,请输入:

pkill -KILL php-cgi

上面的示例将杀死所有用户的 php-cgi 进程。但是,-u 选项将仅杀死其有效用户标识设置为 vivek 的进程:

pkill -KILL -u vivek php-cgi

使 sshd 重新读取其配置文件,输入:

pkill -HUP sshd

案例:受控的 Apache 服务端

创建一个名为 phpjail.sh 的 shell 脚本。此脚本用于在 Jail 中启动 php 服务。这样做是为了提高 Apache 或 Lighttpd Web 服务器的安全性。该脚本演示了 pgrep 命令,pkill 命令以及到目前为止所学的其他技能的用法。

# A shell script to start / stop php-cgi process.
# Author: Vivek Gite <vivek@gite.in>
# Last updated on June-23-2007.
# ----------------------------------------------
fFCGI=/usr/bin/spawn-fcgi
fIP=127.0.0.1
fPORT=9000
fUSER=phpjail
fGROUP=phpjail
fCHILD=10
fJAILDIR=/phpjail
fPID=/var/run/fcgi.php.pid
fPHPCGI=/usr/bin/php-cgi

# path to binary files.
PKILL=/usr/bin/pkill
RM=/bin/rm
PGREP=/usr/bin/pgrep
GREP=/bin/grep
ID=/usr/bin/id

# Must be run as root else die
[ $(${ID} -u) -eq 0 ] || { echo "$0: Only root may run this script."; exit 1; }

# Jail user must exits else die
${GREP} -q $fUSER /etc/passwd || { echo "$0: User $fUSER not found in /etc/passwd."; exit 2; }

# Jail group must exits else die
${GREP} -q $fGROUP /etc/passwd || { echo "$0: Group $fGROUP not found in /etc/group."; exit 3; }

# Jail directory must exits else die
[ ! -d ${fJAILDIR} ] && { echo "$0: php-cgi jail directory \"${fJAILDIR}\" not found."; exit 4; }

# Use case to make decision
case "$1" in

        start)
                # start php-cgi in jail at given IP and server port
                $fFCGI -a $fIP -p $fPORT -u $fUSER -g $fGROUP -C $fCHILD -c $fJAILDIR -P $fPID -- $fPHPCGI
                [ $? -eq 0 ] && echo "Starting php-cgi .. [ OK ]" || echo "Starting  php-cgi .. [ FAILED ]"
                ;;
        stop)
                #  make sure php-cgi is running
		read line < "$fPID"
        	if [ -d /proc/$line ]
        	then
                       # kill php-cgi owned by user
        	       ${PKILL} -KILL -u $fUSER php-cgi
        	       [ $? -eq 0 ] && echo "Stopping php-cgi .. [ OK ]" \
        	                    || echo "Stopping php-cgi .. [ FAILED ]"

                       ${RM} -f $fPID
	        else
        	       echo "$0: php-cgi is not running."
        	fi
                ;;
         status)
                # find out if php-cgi is running or not
                ${PGREP} -u ${fUSER} php-cgi >/dev/null 2>&1
                [ $? -eq 0 ] && echo "$0: php-cgi is running at $fIP:$fPORT" \
                             || echo "$0: php-cgi is not running at $fIP:$fPORT"
                ;;
         *)
                # display usage
                echo "Usage: $0 {start|stop|status}"
esac

Trap

在运行脚本时,用户可以按 Break 或 CTRL + C 终止该过程。用户也可以通过按 CTRL + Z 停止该过程。可能会发生错误,从而导致 Shell 脚本中的错误,例如算术溢出。这可能会导致错误或不可预测的输出。每当用户中断时,信号就会发送到命令或脚本。信号迫使脚本退出。但是,trap 命令捕获一个中断。trap 命令提供脚本来捕获中断(信号),然后在脚本中清除该中断。

trap arg signal
trap command signal
trap 'action' signal1 signal2 signalN
trap 'action' SIGINT
trap 'action' SIGTERM SIGINT SIGFPE SIGSTP
trap 'action' 15 2 8 20

创建一个名为 testtrap.sh 的 shell 脚本:

# capture an interrupt # 0
trap 'echo "Exit 0 signal detected..."' 0

# display something
echo "This is a test"

# exit shell script with 0 signal
exit 0

当脚本试图以状态 0 退出时,第一行设置一个陷阱。然后脚本以 0 退出 shell,这将导致运行 echo 命令。在 shell 提示符下尝试以下示例(确保/tmp/rap54ibs2sap.txt 不退出)。定义一个名为 $file 的 shell 变量:

file=/tmp/rap54ibs2sap.txt

现在,尝试删除 $file,输入:

rm $file

样本输出:

rm: cannot remove `/tmp/rap54ibs2sap.txt': No such file or directory

现在为 rm 命令设置一个 Trap:

trap "rm $file; exit" 0 1 2 3 15

显示已定义 Trap 的列表,输入:

trap

样本输出:

trap -- 'rm /tmp/rap54ibs2sap.txt; exit' EXIT
trap -- 'rm /tmp/rap54ibs2sap.txt; exit' SIGHUP
trap -- 'rm /tmp/rap54ibs2sap.txt; exit' SIGINT
trap -- 'rm /tmp/rap54ibs2sap.txt; exit' SIGQUIT
trap -- 'rm /tmp/rap54ibs2sap.txt; exit' SIGTERM

现在,再次尝试删除 $file,输入:

rm $file

这次 rm 命令未显示错误。$file 尚不存在。trap 命令只要收到 0、1、2、3 或 15 信号就退出。尝试捕获 CTRL + C:

#!/bin/bash
# capture an interrupt # 2 (SIGINT)
trap '' 2
# read CTRL+C from keyboard with 30 second timeout
read -t 30 -p  "I'm sleeping hit CTRL+C to exit..."

样本输出:

I'm sleeping hit CTRL+C to exit...^C^C^C^C

清理 Trap

要清除 Trap,请使用以下语法:

trap - signal
trap - signal1 signal2

例如,为 rm 命令设置一个 Trap:

file=/tmp/test4563.txt
trap 'rm $file' 1 2 3 15
trap

要清除 SIGINT(2),请输入:

trap - SIGINT
trap

要清除所有 Trap,请输入:

trap - 1 2 3 15
trap

创建一个名为 stroreven.sh 的 shell 脚本:

#!/bin/bash
# Shell script to find out odd or even number provided by the user
# ----
# set variables to an integer attribute
declare -i times=0
declare -i n=0

# capture CTRL+C, CTRL+Z and quit singles using the trap
trap 'echo " disabled"' SIGINT SIGQUIT SIGTSTP

# set an infinite while loop
# user need to enter -9999 to exit the loop
while true
do
        # get date
	read -p "Enter number (-9999 to exit) : " n
        # if it is -9999 die
	[ $n -eq -9999 ] && { echo "Bye!"; break; }
        # find out if $n is odd or even
	ans=$(( n % 2 ))
        # display result
	[ $ans -eq 0 ] && echo "$n is an even number." || echo "$n is an odd number."
        # increase counter by 1
	times=$(( ++times ))
done

# reset all traps
trap - SIGINT SIGQUIT SIGTSTP

# display counter
echo "You played $times times."
exit 0

Save and close the file. Run it as follows:

chmod +x oddoreven.sh
./oddoreven.sh

Sample outputs:

Enter number (-9999 to exit) : 2
2 is an even number.
Enter number (-9999 to exit) : 999
999 is an odd number.
Enter number (-9999 to exit) : ^C disabled

0 is an even number.
Enter number (-9999 to exit) : -9999
Bye!
You played 3 times.

函数

您可以将 trap 命令与 shell 函数一起使用,如下所示:

# define die()
die(){
  echo "..."
}

# set trap and call die()
trap 'die' 1 2 3 15
....
...

以下是如何清除 Trap 部分的更新后的 Shell 脚本:

#!/bin/bash
# Shell script to find out odd or even number provided by the user
# set variables to an integer attribute
declare -i times=0
declare -i n=0

# define function
warning(){
  echo -e "\n*** CTRL+C and CTRL+Z keys are disabled. Please enter number only. Hit [Enter] key to continue..."
}

# capture CTRL+C, CTRL+Z and quit singles using the trap
trap 'warning' SIGINT SIGQUIT SIGTSTP

# set an infinite while loop
# user need to enter -9999 to exit the loop
while true
do
	# get date
	read -p "Enter number (-9999 to exit) : " n

	# if it is -9999 die
	[ $n -eq -9999 ] && { echo "Bye!"; break; }

	# $n is 0, just get next number
	[ $n -eq 0 ] && continue

	# find out if $n is odd or evern
	ans=$(( n % 2 ))

	# display result
	[ $ans -eq 0 ] && echo "$n is an even number." || echo "$n is an odd number."

	# increase counter by 1
	times=$(( ++times ))
done

# reset all traps
trap - SIGINT SIGQUIT SIGTSTP

# display counter
echo "You played $times times."
exit 0

以下示例通过更新 /etc/passwd 文件并在 /home 上为用户创建主目录,将用户添加到 Linux 系统。它会捕获各种单一消息,以避免在创建用户帐户时出错。如果用户按下 CTRL + C 或脚本终止,它将尝试回滚对系统文件所做的更改。陷阱在外壳程序脚本中的 useradd 命令之前打开,然后在 chpasswd 行之后关闭陷阱。

#!/bin/bash
# setupaccounts.sh: A Shell script to add user to the Linux system.
# set path to binary files
ADD=/usr/sbin/useradd
SETPASSWORD=/usr/sbin/chpasswd
USERDEL=/usr/sbin/userdel
# set variables
HOMEBASE=/home
HOMEDIR=""
username=""

# define function to clean up useradd procedure
# handle errors using this function
clean_up_useradd(){
    # remove dir
	[ -d $HOMEDIR ]  && /bin/rm -rf $HOMEDIR
	# remove user from passwd if exits
	grep -q "^${username}" /etc/passwd && $USERDEL ${username}
	# now exit
	exit
}

# make sure script is run by root else die
[ $(id -u) -eq 0 ] || { echo "$0: Only root may add a user or group to the system."; exit 1;}

# get username and password
read -p "Enter user name : " username

# create homedir path
HOMEDIR="${HOMEBASE}/${username}"

# capture 0 2 3 15 signals
# if script failed while adding user make sure we clean up mess from
# /home directory and /etc/passwd file
# catch signals using clean_up_useradd()
trap 'clean_up_useradd' SIGINT SIGQUIT SIGTERM

# get password
read -sp "Enter user password : " password

# make sure user doesn't exits else die
grep -q "^${username}" /etc/passwd && { echo "$0: The user '$username' already exits."; exit 2;}


# create a home dir
echo "Creating home directory for ${username} at ${HOMEDIR}..."
[ ! -d ${HOMEDIR} ] && mkdir -p ${HOMEDIR}

# Add user
echo "Adding user ${username}..."
${ADD} -s /bin/bash -d ${HOMEDIR} ${username} || { echo "$0: User addition failed."; exit 3; }


# Set a password
echo "Setting up the password for ${username}..."
#printf "%s|%s\n" $username $password | ${SETPASSWORD} || { echo "$0: Failed to set password for the user."; exit 3; }
echo "$username:$password" | ${SETPASSWORD} || { echo "$0: Failed to set password for the user."; exit 3; }

# reset all traps
trap - 0 SIGINT SIGQUIT SIGTERM

# add rest of the script...

案例:菜单

您可以按以下方式在 shell 脚本中使用 trap 命令。创建一个名为 mainmenu01.sh 的 shell 脚本:


# capture CTRL+C, CTRL+Z and quit singles using the trap
trap 'echo "Control-C disabled."' SIGINT
trap 'echo "Cannot terminate this script."'  SIGQUIT
trap 'echo "Control-Z disabled."' SIGTSTP

# Create infinite while loop
while true
do
    clear
    # display menu
    echo "Server Name - $(hostname)"
	echo "-------------------------------"
	echo "     M A I N - M E N U"
	echo "-------------------------------"
	echo "1. Display date and time."
	echo "2. Display what users are doing."
	echo "3. Display network connections."
	echo "4. Exit"

    # get input from the user
	read -p "Enter your choice [ 1 -4 ] " choice

    # make decision using case..in..esac
	case $choice in
		1)
			echo "Today is $(date)"
			read -p "Press [Enter] key to continue..." readEnterKey
			;;
		2)
			w
			read -p "Press [Enter] key to continue..." readEnterKey
			;;
		3)
			netstat -nat
			read -p "Press [Enter] key to continue..." readEnterKey
			;;
		4)
			echo "Bye!"
			exit 0
			;;
		*)
			echo "Error: Invalid option..."
			read -p "Press [Enter] key to continue..." readEnterKey
			;;
	esac

done
上一页