linux实验五第一部分

  实验五 shell程序

本实验讨论内容

n       linux进程和作业控制的基本命令和操作

n       linux的进程创建、控制和消亡

n       linux进程间通信机制简介

n       linux管道(命名、匿名)通信

n       linux信号通信和信号处理

一、shell与内核的关系

  • Shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。
    • Shell是一个命令解释器,它拥有自己的内建的shell命令集。它解释由用户输入的命令并且把它们送到内核执行。
    • Shell起着协调用户与系统的一致性和在用户与系统之间进行交互的作用。

    Shell的主要版本

bash Bourne Again Shell,Linux使用的默认Shell

tcsh

ksh

 

shell,顾名思义就是包含在Fedora 外的一层,它是Fedora 与用户之间的界面、程序,它接受、解释、执行用户的命令。

shell可以接受的命令有几类:

  • 一类是shell的内部命令,它们包含在shell中,如cdpwd等等,如同DOScommand.com一样。查看内部命令的使用可以用help命令。
  • 另一类是外部命令,它们以可执行文件的方式存放在文件系统某个特定的目录中,如存放在/bin/sbin/usr/bin等目录中,这类命令比如lscprm等命令。 当然,这类应用程序也可以是购买的商业程序、应用软件等等。      

~/.bash_profile

# .bash_profile

# Get the aliases and functions

if [ -f ~/.bashrc ]; then

. ~/.bashrc

fi

# User specific environment and startup programs

PATH=$PATH:$HOME/bin:.

BASH_ENV=$HOME/.bashrc

USERNAME="root"

export USERNAME BASH_ENV PATH

#echo $PATH

二、与进程和作业控制有关的命令和操作

每个进程第一个进程是都有一个识别号,即进程号(PID),系统启动后第一个进程是init,它的PID1,新进程由fork系统调用来产生,旧进程称为父进程,新进程称为子进程。除init进程外,其他进程都有父进程。

init             login            shell         其后用户运行的进程

进程的手动启动方式

Ø       前台启动:手动启动一个进程最常用的一种方式。比如输入“ls –l”,这时就启动了一个前台进程。Shell在前台进程结束之前要一直等待,直到其完成,然后打印出提示符。前台作业的执行优于后台作业。

Ø       后台启动:为了避免耗时较长的进程长期独占终端,我们可以从后台启动一个进程,方法是在命令行后加&,如:

 #  ls –l &

开始执行后台作业后立即打印出提示符,让用户输入新命令。无需等到后台作业完成。

查看进程命令:ps

   要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而ps命令就是最基本同时也是非常强大的进程查看命令。使用该命令可以确定有哪些进程正在运行、运行的状态、进程是否结束、哪些进程占用了过多的资源等等。

   ps命令最常用的还是用于监控后台进程的工作情况,因为后台进程是不和屏幕键盘这些标准输入/输出设备进行通信的,所以如果需要检测其情况,便可以使用ps命令。

ps命令功能

  • 可以确定有哪些进程正在执行和执行的状态
  • 进程是否结束、进程有没有僵死
  • 哪些进程占用了过多的系统资源等

 

ps命令格式

            # ps  [选项]

常用选项

a:显示所有进程(包括其他用户的进程)

e:在命令后显示环境变量

u:显示用户名和启动时间等信息

x:显示没有控制终端的进程

 l:显示状态报告的长列表(14列)

f:显示进程树               w:宽行输出

-e:显示所有进程          -f:显示全部

注意:选项有加“-”的(system V),也有不加“-”(BSD),这是linux/unix不同版本的差别

ps命令输出字段的含义

字 段                  含      义

USER                    进程所有者的用户名

PID                进程号

%CPU                   进程自最近一次刷新以来所占用的CPU

                  间和总时间的百分比

%MEM                 进程使用内存的百分比

VSZ                进程使用的虚拟内存大小,以KB为单位

RSS                当前常驻内存的程序占内存的大小,以

                  KB为单位

TTY                进程相关的终端

STAT                         进程状态,用下面的代码中的一个给出:

R:正在执行的。S:睡眠状态。  D:不间断睡眠。   T:停止或跟踪。Z:僵尸。W:进程没有驻留页       I:空闲。

TIME                     进程使用的总CPU时间

COMMAND           被执行的命令行

NI                  nice value,进程的优先级值,较小的数字意   味着占用较少的CPU时间

PRI                进程优先级。优先级号越小,优先级越高

PPID                     父进程ID

WCHAN         进程等待的内核事件名

操作举例

显示出当前用户在shell下所运行的进程 # ps

只查看用户osmond的进程

# ps -u osmond

列出系统中正在运行的所有进程的详细信息(常用)

# ps -aux

显示系统进程树

# ps -auxf

 

$ps

 PID TTY       TIME CMD

  5755 pts /1    00:00:00 bash

  5828 pts /1    00:00:00 ps

$ ps a

 PID TTY      STAT   TIME COMMAND

  5755 pts /1      S            0:00   bash

  5801 pts /1      S             0:00    z

  5802 pts /1      Z             0:00   [z <defunct>]

  5803 pts /1      R            0:00   ps a

杀死(中止)进程命令:kill

 

为什么要杀死进程

  • 该进程占用了过多的CPU时间
  • 该进程锁住了一个终端,使其他前台进程无法运行
  • 运行时间过长,但没有预期效果
  • 产生了过多到屏幕或磁盘文件的输出
  • 无法正常退出

常用进程信号

  • HUP/SIGHUP/1:从终端上发出的结束信号,发送给守护进程可使其重新读取配置文件
  • INT/SIGINT/2:从键盘上发出的中断信号(ctrl+c
  • QUT/SIGQUT/3:从键盘上发出的退出信号(ctrl+/
  • KILL/SIGKILL/9:结束接受信号的进程(强行杀死进程,必杀)
  • TERM/SIGTERM/15kill命令默认的终止信号
  • STOP/SIGSTOP/19:从键盘来执行的信号(ctrl+d
  • SIGTSTP/20   键盘发出的暂停前台进程的信号(ctrl+z

 

在系统中通过向进程发送进程信号实现对进程的控制。

向进程发送进程信号可以使用killkillall等命令。

 

kill命令

功能:向指定PID的进程发送进程信号(故而常先用ps查出PID)

格式:kill [-signal] <PID>

举例:

杀死PID1621的进程

$ kill 1621

强行杀死PID1621的进程

$ kill -9 1621

killall命令

功能:向指定进程名的进程发送进程信号

格式:killall [-signal] <进程名>

举例:

杀死进程名为cat的所有进程

$ killall cat

强行杀死进程名为named的进程

# killall -9 named

使xinetd守护进程重新读取其配置文件

# killall -1 xinetd

作业控制

o      是指控制当前正在运行的进程的行为,也称为进程控制。

o      作业控制是Shell的一个特性,使用户能在多个独立进程间进行切换。

o      以下命令常用于用户需要在后台运行却意外地把它放在前台启动的时候。

o      作业的定义在教材P137,但是有的书认为挂起或在后台运行的命令叫做作业

 

实施作业控制的常用命令和快捷键

v      cmd &:命令后的&符号表示将该命令放到后台运行,以免霸占终端

v      <Ctrl+d>:终止一个正在前台运行的进程(含有正常含义)

v      <Ctrl+c>:终止一个正在前台运行的进程(含有强行含义)

v      <Ctrl+z>:挂起一个正在前台运行的进程

v      jobs:显示后台作业和被挂起的进程

v       bg [%作业号列表] :在后台恢复运行一个被挂起的进程(即将其切换到后台),不带作业号时,切换当前作业。

v       fg [%作业号]:在前台恢复运行一个被挂起(或在后台运行)的进程,不带作业号时,将当前作业切换到前台。

作业控制举例:

连续使用如下命令:             

 ps

 ctr+z

  jobs

 fg

 kill

   命令的输入和输出

   Linux系统中,执行一个shell命令行时通常会自动打开三个标准文件,即标准输入文件(stdin),通常对应终端的键盘;标准输出文件(stdout)和标准错误输出文件(stderr),这两个文件也都对应终端的屏幕。进程将从标准输入文件中得到输入数据,将正常输出数据输出到标准输出文件,而将错误信息送到标准错误文件中。

   Linux系统为输入、输出的传送引入了另外两种机制,即输入/输出重定向和管道。

  1. 输入重定向

   输入重定向是指把命令(或可执行程序)的标准输入重定向到指定的文件中。输入重定向主要用于改变一个命令的输入源,特别是改变那些需要大量输入的输入源。

由于大多数命令都以参数的形式在命令行上指定输入文件的文件名,所以输入重定向并不经常使用。尽管如此,当要使用一个不接受文件名作为输入参数的命令,而需要的输入内容又存在一个文件里时,就能用输入重定向解决问题。

   输入重定向符“<” 

   cat test1.c     cat <test1.c

  1. 输出重定向

       输出重定向是指把命令(或可执行程序)的标准输出或标准错误输出重新定向到指定文件中。这样,该命令的输出就不显示在屏幕上,而是写入到指定文件中。

   输出重定向比输入重定向更常用,很多情况下都可以使用这种功能。例如,如果某个命令的输出很多,在屏幕上不能完全显示,那么将输出重定向到一个文件中,然后再用文本编辑器打开这个文件,就可以查看输出信息;如果想保存一个命令的输出,也可以使用这种方法。还有,输出重定向可以用于把一个命令的输出当作另一个命令的输入(还有一种更简单的方法,就是使用管道)。

   输出重定向的一般形式为:命令>文件名

   为避免输出重定向中指定文件只能存放当前命令的输出重定向的内容,shell提供了输出重定向的一种追加手段。输出追加重定向与输出重定向的功能非常相似,区别仅在于输出追加重定向的功能是把命令(或可执行程序)的输出结果追加到指定文件的最后,而该文件原有内容不被破坏。 可以使用追加重定向操作符“>>”

   其使用语法形式为:命令>>文件名

举例:

 cat time.h >exam1.txt            exam1.txt

 cat test.c>>exam1.txt            time.h  

test.c

       将一个程序或命令的输出作为另一个程序或命令的输入,有两种方法,一种是通过一个临时文件将两个命令或程序结合在一起;另一种是Linux所提供的管道功能。这种方法比前一种方法更好。

       管道可以把一系列命令连接起来,这意味着第一个命令的输出会作为第二个命令的输入通过管道传给第二个命令,第二个命令的输出又会作为第三个命令的输入,以此类推。显示在屏幕上的是管道行中最后一个命令的输出(如果命令行中未使用输出重定向)。

在一条命令行中当若干命令被“|”分隔开时,这个“|”就被称为管道符号。在这种情况下,shell为一个子命令都创建一个进程,并把他们的输入/输出用管道连接起来

书上p206的例子:

 # ls >/tmp/filelist

 #cat </tmp/filelist

可以改为:

 # ls|cat

含有一个或多个管道的命令会在如下几种情况下产生错误:

  • 当其任意一个子程序出错时
  • 除了第一个子程序外的其他子程序的输入被重定向
  • 除了最后一个子程序以外的其他子程序的输出被重定向

系统调用

系统调用是一个函数调用,它控制状态的改变,系统调用处于操作系统的内核中,是内核的一部分。

使用帮助:

使用man命令(获得命令的手册)

          $ man ls

          $ man man            Q键退出

          使用info命令(获得命令的详细信息)

          $ info ls

          使用help命令(获得Shell内置命令的帮助)

          $ help

          $ help echo

进程

unix/linux提供了一组简洁的系统调用以实现进程的创建和操作:

n        fork 用来创建一个新的进程,该进程是调用进程的子进程(调用进程称为父进程),子进程是父进程的副本。fork是一个基本的进程创建原语。

n        exec函数族:一个函数族,它们对应于同一个系统调用(execve),这些进程的目的是在新进程中执行可执行文件。各种exec函数间的区别主要在于它们的参数。

n        wait:用来实现基本的进程同步。它允许一个进程等待,直至另一个相关进程结束。

n        exit:用来中止一个进程。

fork - create a child process

 #include <sys/types.h>    //定义pid_t(有符号整型)

 #include <unistd.h>

  pid_t fork(void);

 

RETURN VALUE       On success, the PID of the child process is returned  in  the  parent's  thread  of execution, and a 0 is returned in the child's thread of execution.  On failure, a -1 will be returned in the parent's context,  no  child process will be created, and errno will be set appropriately.

 

注意:执行成功,向父进程返回子进程PID,并向子进程返回0,即使调用fork一次,它也会返回两次。如果失败,返回-1

创建子进程的样板代码是:

 pid_t  child;

 if ((child=fork())<0)

   /*错误处理*/

 else if (child==0)

  /*这是新进程*/

 else

 /*这是最初的父进程*/

 

注意:利用fork返回值是程序中区分父子进程的唯一地方。Fork对父进程返回子进程号,对子进程返回0,利用这个区别可以使两个进程执行不同的程序段。

Ø       fork系统调用创建新的进程,子进程是父进程的复制品,两者之间只有很少的差别。新进程号pid和父进程号ppid与原来进程不同。

n       子进程得到父进程的数据段、堆和栈的复制品。(注意:子进程得到的是拷贝,父子进程之间并不共享存储空间。)因此子进程中的变量与其父进程中的变量相同(只有fork的返回值是一个特例),并保持其在父进程中的值。(注意:一个进程后来对变量的改变不影响另一个进程中的变量值)

n       父子进程共享代码段和文件描述符。比如一个进程对文件的读写会影响另一个进程。

n       相同的执行环境、控制终端、umask设置、信号掩码和所有当前信号的处理。

n       父子进程的进程号PID和父进程号PPID不同。

          

            

             

                           exec函数族

#include <unistd.h>

int execl( const char *path, const char *arg, ...);

int execlp( const char *file, const char *arg, ...);

 int execle( const char *path, const char *arg , ..., char * const envp[]);

 int execv( const char *path, char *const argv[]);

int execvp( const char *file, char *const argv[]);

n       exec函数族完成同一功能:装载一个新的程序,并将其转换到调用进程的内存空间。

n       该函数族继承了原来进程的PIDexec函数不返回(除非产生错误时返回-1)。

execve系统调用:

#include <unistd.h>

 int execve (const char *filename, const char *argv [], const char *envp[]);

     

execv

#include <unistd.h>

int execv(const char *path, char *const argv[]);

 execv启动一个新进程,替换原有进程。(不改变PID

 path是要执行的二进制文件或脚本的完整路径。

argv是要传递给程序的完整参数列表,包括arg[0],它一般是可执行程序的名字。该向量是一个指向以空指针(null)结尾的字符串的指针数组。如:

char *argv[]={“/bin/ls”,”-l”,NULL};

 execv(“/bin/ls”,argv)

比较:

 execl( “/bin/ls”,”/bin/ls”,”-l”,NULL)

n       execlpexecvp以外的4exec函数族函数要求它们的第一个参数path必须是一个完整的路径,比如/bin/ls

n       execlpexecvp的第一个参数filename包含“/”字符,就将其视为路径名,否则,就按PATH环境变量,在有关目录中搜寻可执行文件。

                 exit()_exit()

  退出一个程序:

 

#include <stdlib.h>

void  exit(int status);

void  _Exit(int status);  //基本上和_exit()差不多

 

#include <unistd.h>

void _exit(int status);

一个进程在退出(比如使用了exit())后,并不是马上消失,而是留下了一个称为僵尸进程(Zombie)的数据结构。在进程的5种状态中,僵尸进程是非常特殊的一种,它放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程表中留下了一条记录,记录了该进程的退出状态等信息供其他进程收集。同时保持了它的进程号PID

                            waitwaitpid

一旦用forkexec创建了一个新进程,为了搜集新进程的退出状态并防止出现僵尸进程,父进程应该等新进程中止。

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status);

pid_t waitpid(pid_t pid, int *status, int options);

注意:

n        父进程调用wait()后立即阻塞自己,等待任何子进程退出,由wait自动分析是否有当前进程的某个子进程已经退出,如果有,wait就会收集这个进程的信息,并将其彻底销毁后返回;如果没有找到退出的子进程,wait就会一直阻塞在那里,直到出现一个为止。

n       status保存搜集的子进程状态。(<sys/wait.h>中定义了解释status值的宏)如果对这个进程如何死掉并不关心,我们可以设定这个参数为NULL,也就是 pid=wait(NULL);

n       如果成功,wait返回被收集的子进程的进程号pid,如果调用进程没有子进程,wait返回-1,同时errno被置为ECHILD

status变量到底保存了什么东西呢

三个查看status的互斥宏(定义在<sys/wait.h>)(P149):

#include <sys/wait.h>

n       int WIFEXITED(int status);

如果是正常中止子进程返回的状态,则为真。这时可以用WEXITSTATUS(status) 取得子进程传给exit_exit参数的低8位。

n       int WIFSIGNALED(int status);

如果是异常中止子进程返回的状态,则为真。这时可以用WTERMSIG (status)取得使子进程中止的信号编号。

n       WIFSTOPPED

如果是暂停子进程返回的状态,则为真。这时可以用WSTOPSIG (status)取得使子进程中止的信号编号。

WIFEXITED

Query status to see if a child process ended normally .This macro queries the child termination status provided by the wait and waitpid functions, and determines whether the child process ended normally.

#include <sys/wait.h>

int WIFEXITED(int status);

status :The status field that was filled in by the wait or waitpid function.

The WIFEXITED macro is always successful. If the child process for which status was returned by the wait or waitpid function exited normally, the WIFEXITED macro evaluates to TRUE and the WEXITSTATUS macro is used to query the exit code of the child process. Otherwise, the WIFEXITED macro evaluates to FALSE.

WIFSIGNALED

Query status to see if a child process ended abnormally .This macro queries the child termination status provided by the wait and waitpid functions. It determines if the child process exited because it raised a signal that caused it to exit. It is also used with the WTERMSIG macro to determine if the child process exited because of a system error.

 

#include <sys/wait.h>

int WIFSIGNALED(int status);

status :The status field that was filled in by the wait or waitpid function.

If the child process for which status was returned by the wait or waitpid function exited because it raised a signal that caused it to exit, the WIFSIGNALED macro evaluates to TRUE and the WTERMSIG macro can be used to determine which signal was raised by the child process. Otherwise, the WIFSIGNALED macro evaluates to FALSE.

 

WTERMSIG

Determine which signal caused the child process to exit .

This macro queries the termination status of a child process to determine which signal caused the child process to exit. Status is provided by the wait and waitpid functions.

#include <sys/wait.h>

 int WTERMSIG(int status);

status :The status field that was filled in by the wait or waitpid function.

If the WIFSIGNALED macro indicates that the child process exited because it raised a signal, the WTERMSIGmacro returns the numeric value of the signal that was raised by the child process. Signal values are defined in the sys/signals.h C header file. A signal value of SIGILL means the child process exited because of a system error rather than the raising of a particular signal. If the WIFEXITED macro indicates that the child process did not exit because it raised a signal, the value returned by the WTERMSIG macro has no meaning.

WIFSTOPPED

Check whether the process is currently stopped.

function WIFSTOPPED(

  Status: Integer

):Boolean;

Description

WIFSTOPPED checks Status and returns true if the process is currently stopped.

waitpid:

 pid_t waitpid(pid_t pid, int *status, int options);

n       pid>0 等待进程号是pid的子进程;

n       pid=-1 等待任何子进程退出,这时waitpidwait的作用是一样的。

n        pid=0 等待任何进程组ID与父进程的进程组ID相等的子进程。

n        pid<-1 等待任何进程组IDpid绝对值相等的子进程。

n       options可以是WNOHANG,此时waitpid在没有子进程退出时就立即返回,此时函数返回值是0。否则父进程等子进程退出才停止执行waitpid。如果不想使用options,也可以将其置0

n       返回值为退出的子进程进程号,如设置WNOHANG,返回0,如果出错返回-1

unistd.h中,

 static inline pid_t wait(int *wait_stat)

{ return waitpid (-1,wait_stat,0);}

wait函数族允许父进程等待子进程执行结束。它会在子进程状态改变时返回。而不仅仅在子进程运行结束或者退出时才返回。

僵尸进程和孤儿进程

n       僵尸进程:一个子进程在其父进程还没有调用wait()waitpid()的情况下退出。这个子进程就是僵尸进程。

n       孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

#include <sys/types.h>

#include <unistd.h>

main()

{

      pid_t pid;

         pid=fork();

       if(pid<0) /* 如果出错 */

           printf("error occurred!/n");

       else if(pid==0) /* 如果是子进程 */

          exit(0);

       else        /* 如果是父进程 */

         sleep(20);   /* 休眠60秒,这段时间里,父进程什么也干不了 */

        wait(NULL);       /* 收集僵尸进程 */

}

维持20秒钟的僵尸进程

每个进程都有唯一的进程号PID,下面两个系统调用可以获得当前进程的PID和父进程的PID(也就是PPID

#include <sys/types.h>

#include <sys/wait.h>

pid_t getpid(void);   //返回当前进程的PID

pid_t getppid(void); //返回当前进程的父进程的PID

#include<unistd.h>

int main(){

   printf("The current process ID is %d/n",getpid());

  exit0);

}

书上p83的例子

int main(int argc, char *argv[])

    {

      int status;

      int pid;

      char *prog_argv[4];

 

      /* Build argument list */

 

      prog_argv[0] = "/usr/local/bin/ls";

      prog_argv[1] = "-l";

      prog_argv[2] = "/";

      prog_argv[3] = NULL;

 

if ((pid=fork()) < 0)

      {

        perror ("Fork failed");

        exit(errno);

      }

 

      if (!pid)

      {

        /* This is the child, so execute the ls */

        execvp (prog_argv[0], prog_argv);

      }

 

      if (pid)

 

      {

        /*

         * We're in the parent; let's wait for the child to finish

         */

        waitpid (pid, NULL, 0);

      }

shell是如何运行程序的

 

 

 

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值