2. Linux进程(记录)

2. Linux进程

perror ()函数的功能是打印一个系统错误信息。 当程序的当前函数(这类函数包括系统函数和库函数)出现错误,会将错误值保存在errno这个全局变量中,然后当程序执行到perror ()函数的时候,会先打印参数s中的字符串,接着打印一个冒号,和errno值对应的错误描述字符串。 因此在使用perror ()函数的时候也要引用头文件errno.h,因为errno这个全局变量定义在这个头文件中。

2.1进程的基本概念

  • 进程是处于运行状态的程序。
  • 一个源程序经过编译、链接后,成为一个可以运行的程序。
  • 当该可执行的程序被系统加载到内存空间运行时,就成为进程。
  • 程序是静态的保存在磁盘上的代码和数据的组合,而进程是动态的概念。
2.1.1进程的属性

进程创建后,系统内核为其分配了一系列的数据结构,其保存了进程的相关属性。

主要的进程属性:

  • 进程的标识符:进程创建时,内核为每一个进程分配一个唯一的进程标识符。取值0~32767。进程ID是由系统循环使用的。(超过最大值会从0选择可用的整数继续循环使用)

  • 进程的父进程标识符:Linux下的全部进程组成一棵进程树。其中树根进程是0号进程swapper。除根进程外,每个进程都有其对应的父进程。

  • 进程的用户标识:

  • 进程组的组标识

  • 进程的有效用户标识

  • 进行的有效组标识

  • 进程的进程组标识符

  • 进程的会话标识符

可以通过命令ps查看。

yang@yang-virtual-machine:~/桌面$ ps -alef|more
F S UID          PID    PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME 
CMD
4 S root           1       0  0  80   0 - 41979 -      2月03 ?       00:00:07 /
sbin/init splash
1 S root           2       0  0  80   0 -     0 -      2月03 ?       00:00:00 [
kthreadd]
1 I root           3       2  0  60 -20 -     0 -      2月03 ?       00:00:00 [
rcu_gp]
1 I root           4       2  0  60 -20 -     0 -      2月03 ?       00:00:00 [
rcu_par_gp]
1 I root           5       2  0  60 -20 -     0 -      2月03 ?       00:00:00 [
slub_flushwq]
1 I root           6       2  0  60 -20 -     0 -      2月03 ?       00:00:00 [
netns]
1 I root           8       2  0  60 -20 -     0 -      2月03 ?       00:00:00 [
kworker/0:0H-events_highpri]
1 I root          10       2  0  60 -20 -     0 -      2月03 ?       00:00:00 [
mm_percpu_wq]
1 S root          11       2  0  80   0 -     0 -      2月03 ?       00:00:00 [
--更多--
  • 第一行,ps命令输出的头部信息,用于标明后续列出的进程属性数据的含义。
  • PID进程标识符,PPID父进程标识符

Linux支持通过编程的方式获取进程的属性,包括获取进程ID的getpid,获取进程父进程ID的getppid,获取进程用户标识的getuid,获取进程有效用户标识geteuid等。头文件<unistd.h>

#include<unistd.h>
__pid_t getpid(void);  //获取当前进程的进程的ID
__pid_t getppid(void);
__pid_t getpgrp(void);
__pid_t getuid(void);
__pid_t geteuid(void);
__pid_t getgid(void);
__pid_t getegid(void);
__pid_t getsid(__pid_t__pid);

返回-1,调用失败,查看errno获取详细错误信息。成功返回获取到进程属性信息。

yang@yang-virtual-machine:~/桌面/c$ vim jcxx.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o jcxx.out jcxx.c
yang@yang-virtual-machine:~/桌面/c$ ./jcxx.out
process id=4242
parent process id=3953
process group id=4242
calling process's real user id=1000
calling process's real group id=1000
calling process's effective user id=1000
calling process's effective group id=1000
#include<stdio.h>
#include<unistd.h>
int main()
{
        printf("process id=%d\n",getpid());
        printf("parent process id=%d\n",getppid());
        printf("process group id=%d\n",getpgrp());
        printf("calling process's real user id=%d\n",getuid());
        printf("calling process's real group id=%d\n",getgid());
        printf("calling process's effective user id=%d\n",geteuid());
        printf("calling process's effective group id=%d\n",getegid());
        return 0;
}

通常情况下,进程的用户标识与有效用户标识是相同的,但是对于suid程序来说,其有效用户ID与用户ID是不同的。/usr/bin/passwd即是一个suid程序,用户id是运行用户的ID,而其有效用户ID则是超级用户root。

2.1.2进程的内存映像

一个可执行程序被系统加载后,成为一个进程。在系统内存映像中,进程从低地址到高地址,依次是代码段、数据段、BSS段、堆栈段、命令行参数及环境变量。

  • 代码段:用来存放可执行文件的指令,是可执行程序在内存中的映像。只读。访问有严格安全检查机制,防止在运行时被非法修改。
  • 数据段:存放已被初始化的全局变量。
  • BSS段:BSS段包含程序中未初始化的全局变量。
  • 堆(heap):进程空间内的内存区,用于进程运行过程中动态内存分布。大小不固定,根据要求动态变化。当程序中调用malloc函数等函数申请内存时,新申请的内存被动态添加到堆内;当用free等函数释放内存内存时,被释放的内存从堆中被删除。
  • 栈(steak):存放程序中的局部变量的内存区,以及用于保存函数调用时的现场。调用函数时,函数的参数被压入栈,由函数从栈中取出。函数返回时,将返回值压入栈,由主调函数从栈中取出。栈由系统自动分配,用户程序不需要关心其分配及释放。
2.1.3进程组
  • Linux中,每个进程唯一归属于某个进程组。
  • 在shell环境中,一条Linux命令就形成一个进程组。这条命令可以只包含一个命令,也可以是通过管道符连接起来的若干命令。
  • 每个进程组都有一个组长进程。组长进程ID=进程组ID。
  • 当进程组内的所有进程都结束或者加入到其他进程组内时,该进程组就结束了。

通过系统调用setgid修改某个进程的进程组。头文件<unistd.h>

#include<unistd.h>
int setpgid(__pid_t __pid,__pid_t __pgid);
  • __pid:输入参数,用于指定要修改的进程ID。参数为0,则指定当前进程ID。

  • __pgid:输入参数,用于指定新的进程组ID。如果为0,则指当前进程ID。

  • 返回0,调用成功。返回-1,调用失败。

/*调用setpgid使本进程成为新进程组的组长。*/
#include<stdio.h>
#include<unistd.h>
int main()
{
        setpgid(0,0);
        sleep(10);/*休眠10秒,以供查看进程状态*/
        return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim jcxx.c
yang@yang-virtual-machine:~/桌面/c$ vim xjcz.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o xjcz.out xjcz.c
yang@yang-virtual-machine:~/桌面/c$ ./xjcz.out
yang@yang-virtual-machine:~/桌面/c$ ps -ao pid,pgrp,cmd#查看失败
    PID    PGRP CMD
   1158    1154 /usr/libexec/gnome-session-binary --session=ubuntu
   4441    4441 ps -ao pid,pgrp,cmd
yang@yang-virtual-machine:~/桌面$ ps -ao pid,pgrp,cmd
    PID    PGRP CMD
   1158    1154 /usr/libexec/gnome-session-binary --session=ubuntu
   5759    5759 ./xjcz.out
   5760    5760 ps -ao pid,pgrp,cmd

setpgrp函数功能=setpgid(0,0),即设置当前进程为进程组组长。

2.1.4进程的对话
  • 用户登录一个新的shell环境时,产生一个新的会话。
  • 一个会话可以包括若干进程组,但这些进程组只能有一个前台进程组,其他为后台进程组。
  • 前台进程组通过其组长进程与控制终端相连接,接收来自控制终端的输入即信号。
  • 一个会话由会话ID来标识,会话ID是会话首进程的进程ID。

setsid:用于产生一个新的会话。

  • 调用setsid的进程不能是某个进程组的进程ID。
  • 新会话的会话ID是调用进程的进程ID。
  • 新会话只包含一个进程组,该进程组只包含一个进程(调用setsid的进程),该会话没有控制终端。
#include<unistd.h>
__pid_t setsid(void);
  • 返回-1,调用失败。典型错误EPERM:调用进程是某个进程组的组长。
  • 成功返回进程的进程组ID。
/*调用setsid实现进程的后台运行*/
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>/*exit头文件*/
int main()
{
        int n;/*循环变量定义*/
        __pid_t nPid;/*进程ID变量*/
        __pid_t nGroupId;/*进程的组长进程ID*/
        if((nPid=fork())<0){/*创建新的子进程*/
                perror("fork");/*创建子进程失败,错误处理*/
                exit(0);
        }
        if(nPid!=0)/*父进程*/
        {
                exit(0);/*退出父进程*/
        }
        nGroupId=setsid();/*产生新会话,返回新创建的进程组的组ID*/
        if(nGroupId==-1){/*错误处理*/
                perror("setsid");/*输出错误信息*/
                exit(0);
        }
        for(n=0;n<10;n++)/*循环一段时间退出,供用户查看运行结果*/
                sleep(3);/*休眠3秒*/

        return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim setsid.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o setsid.out setsid.c
yang@yang-virtual-machine:~/桌面/c$ ./setsid.out
yang@yang-virtual-machine:~/桌面/c$ ps -ao pid,ppid,pgrp,session,tty,cmd#查看失败,不知道为什么没有休眠
    PID    PPID    PGRP    SESS TT       CMD
   1158    1154    1154    1154 tty2     /usr/libexec/gnome-session-binary --se
   4639    3953    4639    3953 pts/1    ps -ao pid,ppid,pgrp,session,tty,cmd

用户登录的shell进程就是前台进程,后台进程一般无控制终端。

2.1.5进程的控制终端
  • 作为多用户、多任务的操作系统,Linux支持多个用户同时从终端登录系统。

  • Linux终端类似于Windows环境下的远程桌面连接。

  • 用户通过终端输入请求,提交给主机运行并显示主机的运行结果。

  • 传统的Linux终端是由RS232串口通信协议的串口终端,终端与主机间的通讯通过主机的串口进行。

    • 这种串口终端数据传输速度较慢,并且传输距离有限,现在已经逐渐被网络终端替代。
  • 网络终端与主机之间通过以太网连接,数据传输速度大为提升。

  • Linux系统中,每个终端设备都有一个设备文件与其相关联,这些终端设备称为tty。

  • 在shell环境下,可以通过执行命令tty查看当前终端的名称。

  • 用户可以通过telnet远程登录到某个Linux系统,此时没有真正的终端设备。Linux系统为用户自动分配了一个称为“伪终端”的终端设备,设备名称类似/dev/pts/???。

前面介绍了Linux系统的终端环境。

  • 在Linux的进程环境中,有”控制终端“的概念。
  • 控制终端,是指一个进程运行时,进程与用户进行交互的界面。
  • 一个进程从终端启动后,这个进程的运行过程就与控制终端密切相关。
  • 可以通过控制终端输入/输出,也可以通过其向进程发送信号(+键中止程序运行)。
  • 控制终端被关闭时,该控制终端所关联的进程将收到SIGHUP信号。
  • 系统对SIGHUP信号的(缺省)处理方式就是中止进程。

通过执行ps -ax查看进程的控制终端,TTY列有值表明进程有控制终端,否则没有。

2.1.6进程的状态

Linux的进程是由操作系统内核调度运行的。在调度过程中,进程的状态是不断变化的。

主要包括可运行状态、等待状态(也称为睡眠状态)、暂停状态、僵尸状态、退出状态等。

  • 可运行状态:包括就绪状态和执行状态,只有处于执行状态的进程是真正占用CPU的进程。就绪状态,进程处于预备运行状态,在等待系统按照时间片轮转规则将CPU分配给它。
  • 等待状态:进程正在等待某个事件发生后等待某种资源。分为可中断的和不可中断的。
    • 处于可中断等待状态的进程,既可以被信号中断,也可以由于资源就绪而被唤醒进入运行状态。
    • 处于不可中断等待状态的进程,只能后者。
  • 暂停状态:进程接收到某个信号,暂时停止运行。大多数进程是由于处于调试中,才会出现该状态。
  • 僵尸状态:进程结束但未消亡的一种状态。进程会在退出前向其父进程发送SIGCLD信号。父进程应该调用wait为子进程的退出做最后的收尾工作。如果父进程未进行该工作,则子进程虽然已经退出,但通过ps命令仍然可以看到该进程,其状态就是僵尸状态。应用编程中,应该尽量避免僵尸进程的出现。

Linux系统下,ps命令可以查看进程状态,输出的是静态的,是进程的某一时刻的信息;进程查看工具top可以持续的动态的输出进程的信息。

yang@yang-virtual-machine:~/桌面/c$ top

top - 00:41:58 up  5:40,  1 user,  load average: 0.00, 0.01, 0.00
任务: 296 total,   1 running, 295 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.7 us,  1.5 sy,  0.0 ni, 97.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   3889.8 total,   1067.1 free,   1291.2 used,   1531.6 buff/cache
MiB Swap:   3898.0 total,   3898.0 free,      0.0 used.   2306.6 avail Mem 

 进程号 USER      PR  NI    VIRT    RES    SHR    %CPU  %MEM     TIME+ COMMAND                                                                                                                                                                              
   1256 yang      20   0 4499144 277420 136852 S   3.7   7.0   0:55.57 gnome-shell                                                                                                                                                                          
   2453 yang      20   0  668800  83632  63516 S   1.7   2.1   0:09.29 gnome-terminal-                                                                                                                                                                      
    409 root     -51   0       0      0      0 S   0.3   0.0   0:00.99 irq/16-vmwgfx                                                                                                                                                                        
    783 root      20   0  326388   9040   7532 S   0.3   0.2   0:15.55 vmtoolsd                                                                                                                                                                             
   1468 yang      20   0  326180  17200   7444 S   0.3   0.4   0:03.32 ibus-daemon                                                                                                                                                                          
   1578 yang      20   0  297636  38716  30088 S   0.3   1.0   0:15.78 vmtoolsd                                                                                                                                                                             
      1 root      20   0  166708  11900   8260 S   0.0   0.3   0:03.95 systemd                        
  • 第一行:当前时间(00:41:58)、系统启动时间(5:40)(即00:40:05)、当前登录用户数目(1 user)、平均负载(load average: 0.00, 0.01, 0.00)
  • 第二行:当前进程的汇总信息,包括当前进程总数(296 total), 运行进程数(1 running),休眠进程数(295 sleeping),终止进程数(0 stopped), 僵死进程数(0 zombie)
  • 第三行:当前CPU的消息情况,用户占用(0.7 us)、系统占用(1.5 sy)、优先进程占用(0.0 ni)、闲置进程占用(97.8 id)
  • 第四行:内存状态汇总信息,分别为平均可用内存(3889.8 total)、空闲内存(1067.1 free), 已用内存(1291.2 used),缓存使用内存(1531.6 buff/cache)
  • 第五行:交换区状态,平均可用交换容量3898.0 total, 闲置容量 3898.0 free, 已用容量 0.0 used. 高速缓存容量2306.6 avail Mem
  • 第六行:进程明细信息的标题,进程号PID,进程所有者用户USER,进程的优先级别PR,进程的谦让度值NI,进程占用的虚拟内存值VIRT、进程占用的物理内存值RES、进程使用的共享内存值SHM,进程的状态S,进程占用的CPU使用率(%CPU)、进程占用的物理内存的百分比(%MEM)、进程启动后占用的总的CPU时间(TIME+)、进程启动的启动命令行(COMMAND)
  • 第七行开始:系统中各进程的详细信息。每隔一定时间被刷新一次。
2.1.7进程的优先级

多任务的操作系统,Linux允许多个进程同时运行。

  • 但是,在CPU数量少于同时运行的进程数量时,是不可能实现真正同时运行的。
  • 以单CPU的计算机为例,如果系统中同时存在两个进程同时运行,则实际上在同一时刻只可能有一个进程占用CPU运行。

为实现多任务的目标,Linux使用了一种称为”时间片轮转“的进程调度方式,为每一个进程指派一定的运行时间。

  • 这个时间片通常很短,以毫秒甚至更小的时间级别为单位。
  • 系统核心依照某种规则,从大量进程中选择一个进程投入运行,其余的进程暂时等待。
  • 当正在运行的那个进程时间片用完,或进程执行完毕退出,Linux就会重新进行进程调度,挑选下一个可用进程投入运行。
  • 由于每个进程运行时占用的时间片很短,在用户的角度看来,就如同多个进程同时运行一样。

进程的优先级定义了被调度的优先顺序。

  • 优先级的数值越低,进程越是优先被调度。
  • 优先级是由进程的优先级别(PR)和进程的谦让值(NI)两个因素联合确定的。一般相加来确定进程的真正优先级别。
  • 对于一个进程,其优先级是由父进程继承而来的,用户进程不可更改。

1.nice

为方便用户修改进程运行的优先级,Linux提供了nice系统调用以修改进程的谦让值。进程的谦让值在进程被创建时设置为缺省值为0。系统允许的谦让值范围为最高优先级的-20到最低优先级的19。

nice原型:

#include<unistd.h>
int nice(int __inc);
  • __inc:输入参数,指定新的谦让值。-20~19.

  • 返回0,调用成功。返回-1,调用失败。

  • 注意:只有超级用户可用指定负的谦让值。(只有超级用户可以提高进程的调度优先级别。)不是超级用户而指定负的谦让值,则nice返回-1,errno为ERERM。

2.setprioity

nice系统调用只能修改进程自身的谦让值,而setpriority则可以修改其他进程甚至一个进程组的谦让值。

原型:

#include<sys/resource.h>
int setpriority(__priority_which_t __which,id_t __who,int __prio);
  • __which:指定设置谦让值的目标类型。PRIO_PROCESS(为某进程设置谦让值)、PRIO_RGRP(为某进程组设置谦让值)、PRIO_USER(为某个用户的所有进程设置谦让值)。
  • __who:设置谦让值的目标。进程ID、进程组ID或者用户ID。参数为0,表示当前进程、当前进程组或者当前用户。
  • __prio:要设置的谦让值,范围-20~19。只有超级用户才可以用负值调用setpriority。
  • 返回0,调用成功;返回-1,调用失败。

getpriority

在进程的谦让值被修改后,得到进程的谦让值。

原型:

#include<sys/resource.h>
int getpriority(__priority_which_t __which,id_t __who);
  • 其返回值比较特殊,可能返回负值,所以不能直接根据返回值确定是否调用成功。建议调用getpriority前设置errno为0,如果调用后errno为0,表明成功,否则表明失败。
/*修改进程的谦让值,调用完成后输出进程的谦让值。*/
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/resource.h>
int main()
{
        int nPr;
        if(nice(3)==-1){/*降低优先级*/
                perror("nice");
                exit(0);
        }
        errno=0;
        nPr=getpriority(PRIO_PROCESS,getpid());
        if(errno!=0){
                perror("getpriority");
                exit(0);
        }
        printf("priority is %d\n",nPr);
        return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim qrz.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o qrz.out qrz.c
yang@yang-virtual-machine:~/桌面/c$ ./qrz.out
priority is 3

此外,nice可以在执行一个程序时,直接指定谦让值,而rnice命令则可以修改一个正在运行的进程的谦让值。

2.2进程的运行环境

2.2.1进程的入口函数

1.main函数

C语言的入口函数。进程从main函数开始执行。

int main(int argc,char *argv[],char *env[]);/*格式全面的main函数*/
  • argc:表明程序执行时的命令行参数个数(该参数个数包含了程序名称本身)。执行程序时,未在命令行输入任何参数,则argc值为0。该参数的值是由系统确定的,用户程序可以根据该值获得程序执行时命令行参数的个数。
  • argv:命令行参数数组。其中每一个数组成员为一个命令行参数。程序名称是该数组的第一个成员argv[0]。在Linux系统中,各命令行参数是以空格分隔的。
  • env:环境变量数组,可以在程序中访问这些环境变量。
  • main函数返回值可以在shell中获取到。
  • main函数可以有多种格式。不需要对命令行参数进行处理,可以使用main()。
#include<stdio.h>
int main(int argc,char *argv[],char *env[])
{
        int i;
        for(i=0;i<argc;i++)
                printf("argv[%d]=%s\n",i,argv[i]);
        for(i=0;env[i]!=NULL;i++)
                printf("env[%d]=%s\n",i,env[i]);
        return 5;
}
yang@yang-virtual-machine:~/桌面/c$ vim main.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o main.out main.c
yang@yang-virtual-machine:~/桌面/c$ ./main.out aa bbb cccc
argv[0]=./main.out
argv[1]=aa
argv[2]=bbb
argv[3]=cccc
env[0]=SHELL=/bin/bash
env[1]=SESSION_MANAGER=local/yang-virtual-machine:@/tmp/.ICE-unix/1233,unix/yang-virtual-machine:/tmp/.ICE-unix/1233
env[2]=QT_ACCESSIBILITY=1
env[3]=COLORTERM=truecolor
env[4]=XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg
env[5]=SSH_AGENT_LAUNCHER=gnome-keyring
env[6]=XDG_MENU_PREFIX=gnome-
env[7]=GNOME_DESKTOP_SESSION_ID=this-is-deprecated
env[8]=LANGUAGE=zh_CN:zh
env[9]=GNOME_SHELL_SESSION_MODE=ubuntu
env[10]=SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
env[11]=XMODIFIERS=@im=ibus
env[12]=DESKTOP_SESSION=ubuntu
env[13]=GTK_MODULES=gail:atk-bridge
env[14]=PWD=/home/yang/桌面/c
env[15]=LOGNAME=yang
env[16]=XDG_SESSION_DESKTOP=ubuntu
env[17]=XDG_SESSION_TYPE=wayland
env[18]=SYSTEMD_EXEC_PID=1256
env[19]=XAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.UWPCZ1
env[20]=HOME=/home/yang
env[21]=USERNAME=yang
env[22]=IM_CONFIG_PHASE=1
env[23]=LANG=zh_CN.UTF-8
env[24]=LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
env[25]=XDG_CURRENT_DESKTOP=ubuntu:GNOME
env[26]=VTE_VERSION=6800
env[27]=WAYLAND_DISPLAY=wayland-0
env[28]=GNOME_TERMINAL_SCREEN=/org/gnome/Terminal/screen/cd014f02_ba75_4f90_a478_b8a3d4b6f5bb
env[29]=GNOME_SETUP_DISPLAY=:1
env[30]=LESSCLOSE=/usr/bin/lesspipe %s %s
env[31]=XDG_SESSION_CLASS=user
env[32]=TERM=xterm-256color
env[33]=LESSOPEN=| /usr/bin/lesspipe %s
env[34]=USER=yang
env[35]=GNOME_TERMINAL_SERVICE=:1.97
env[36]=DISPLAY=:0
env[37]=SHLVL=1
env[38]=QT_IM_MODULE=ibus
env[39]=XDG_RUNTIME_DIR=/run/user/1000
env[40]=XDG_DATA_DIRS=/usr/share/ubuntu:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop
env[41]=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin
env[42]=GDMSESSION=ubuntu
env[43]=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
env[44]=_=./main.out
env[45]=OLDPWD=/home/yang/桌面
yang@yang-virtual-machine:~/桌面/c$ echo $?
5
  • 环境变量也可以通过shell执行命令env获取。
  • Linux环境下,有多个shell系统变量:
    • $#:命令行参数个数
    • $n:命令行参数,n为非负整数,$0表示程序名称,$1表示第一个命令行参数
    • $?:前一条命令的返回值
    • $$:本进程的进程ID
    • $!:上一个进程的进程ID

2.getopt

  • 专门的系统调用getopt获取命令行参数。getopt提供更为强大的获取命令行参数的方法。

  • 不仅可以获取命令行参数,而且可以按照规则解析命令行参数。

  • 执行下述程序:./test_program -i -s abc -t,在这种情况下,要获取到-s选项的参数abc需要进行很复杂的判断。而getopt可以很简单地解决这一问题。

  • 原型:

#include<getopt.h>
int getopt(int __argc,char *const *__argv,const char *_shortopts);
int getopt_long (int __argc,char *const *__argv,const char *_shortopts,const option *_longopts,int *_longind);
  • __argc:输入参数,即main函数的argc。

  • __argv:输入参数,即main函数的argv。

  • _shortopts:输入参数,选项字符串。

    • getopt认可的命令行选项参数是通过”-“进行的。例如:ls -l中的”-l“。
    • 该参数中,如果某个选项有输入数据,则在该选项字符的后面应该包含”:“,如ls -l ./a,在指定本参数时,应该用”l:“。
    • 如果该参数的第一个字符是”:“,则getopt发现无效参数后并不返回失败,而是返回”?”或者“:”。返回“?”表示选项无效,返回“:”表示需要输入选项值。
  • getopt只能支持单字符的选项,如-l、-a等。

  • 需要支持多字符的选项,如-file等,就需要用到getopt_long。

    • getopt_long通过指定一个struct option类型的结构数组,将多字符的选项映射为单个字符,从而实现了对多字符选项的支持。

    • struct option
      {
      	const char *name;
          int has_arg;
          int *flag;
          int val;
      }
      
      • name:定义了多字符的选项名称。

      • has_arg:定义了是否有选项值,值为0没有选项值,为1有选项值,为2表明选项值可有可无。

      • flag:如果该成员定义为NULL,那么调用getopt_long的返回值为该结构val字段值;如果该成员不为NULL,getopt_long调用后,将在该参数所指向的变量中填入val值,并且getopt_long返回0。通常该成员定义为NULL即可。(?)

      • val是该长选项对应的短选项名称。(?)

      • __longind:输出参数,如果该参数没有设置为NULL,那么它是一个指向整型变量的指针。在getopt_long运行时,该整型变量会被赋值为获取到的选项在结构数组 __longopts中的索引值

      • 返回值:

        • ?:表明getopt返回一个未在__shortopts定义的选项。

        • ::表明该选项需要选项值,则实际未输入选项值。

        • -1:表明getopt解析完毕,后面已经没有选项。

        • 0:在getopt_long的结构数组参数__longopts中的成员flag定义了值。此时,getopt_long返回0,而选项的参数将存储在flag所指向的变量中。

        • 其他:返回的选项字符。

  • 在使用getopt时,需要注意与该系统调用相关的全局变量的使用,详细说明:

    • optind:整型变量,存放环境变量数组argv的当前索引值。当调用getopt循环取选项结束(返回-1)后,剩余的参数在argv[optind]~argv[argc-1]中。
    • optarg:字符串指针,当处理一个带有选项值的参数时,全局变量optarg将存放该选项的值。
    • optopt:整型变量,调用getopt发现无效的选项(返回?或者:)时,此时optopt包含了当前无效的选项。
    • opterr:整型变量,调用getopt前设置该变量为0,则getopt在发现错误时不输出任何信息。

接收选项的程序:

短选项长选项说明
-f–flag输入标志
-nusername–name
#include<stdio.h>
#include<getopt.h>
int save_flag_arg;/*用于存储用getopt_long时返回的短选项值*/
char *opt_arg_value;/*用于保存系统全局变量中存储的当前选项的值*/
struct option longopts[]={
        {"flag",no_argument,&save_flag_arg,'f'},/*选项“flag”不需要输入参数*/
        {"name",required_argument,NULL,'n'},/*选项“name”需要输入参数,*//*如果指定需要输入参数,则应该在getopt_long中的__shortopts参数中的短选项后输入“:”*/
        {NULL,0,NULL,0},
};
int main(int argc,char *argv[])
{
        int i,c;
        while((c=getopt_long(argc,argv,":n:f",longopts,NULL))!=-1)/*循环调用getopt_long解析输入选项*/
        {
                switch(c)
                {
                        /*输入选项为n或者name的处理。此时,optarg中保存的是输入选项的值。*/
                        case 'n':
                                opt_arg_value=optarg;
                                printf("name is %s.\n",opt_arg_value);
                                break;
                        /*getopt_long返回值为0,表明结构数组中有成员指定了flag。此时,flag指针中						存储的是单字节选项。注意,此时getopt_long返回值并不是选项。*/
                        case 0:
                                if(save_flag_arg=='f')
                                {
                                        printf("flag argument found!\n");
                                }
                                break;
                        /*选项需要选项值而没有输入*/
                        case ':':
                                printf("argument %c need value.\n",optopt);
                                break;
                        /*该选项无效*/
                        case '?':
                                printf("Invalid argument %c!\n",optopt);
                                break;
                }
        }
        return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim getopt.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o getopt.out getopt.c
yang@yang-virtual-machine:~/桌面/c$ ./getopt.out
yang@yang-virtual-machine:~/桌面/c$ ./getopt.out -name Hubery -flag
name is ame.
Invalid argument l!
Invalid argument a!
Invalid argument g!
yang@yang-virtual-machine:~/桌面/c$ vim getopt.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o getopt.out getopt.c
yang@yang-virtual-machine:~/桌面/c$ ./getopt.out -name Hubery -flag
name is ame.
Invalid argument l!
Invalid argument a!
Invalid argument g!
yang@yang-virtual-machine:~/桌面/c$ ./getopt.out --name Hubery --flag
name is Hubery.
flag argument found!
yang@yang-virtual-machine:~/桌面/c$ ./getopt.out =name Hubery =flag
yang@yang-virtual-machine:~/桌面/c$ ./getopt.out --name Hubery --flag
name is Hubery.
flag argument found
yang@yang-virtual-machine:~/桌面/c$ ./getopt.out --flag
flag argument found!
yang@yang-virtual-machine:~/桌面/c$ ./getopt.out --name Hubery 
name is Hubery.
  • 使用多字符格式的选项输入,应该用“–”作为选项的前导符。
  • 使用单字符格式的选项输入,“-”和“–”都可以。
2.2.2进程的环境变量

对于每个Linux系统中的进程来说,都有与进程相关的环境变量。环境变量可以用于保存一些重要的配置信息。

如用户在编程过程中,需要保存某个配置项的值,一个方法是将其写入到文件中,而更好的方法是定义成环境变量。

当用户登录shell时,会从两个位置获得环境变量的定义。

  • 1.全局环境变量文件/etc/profile
    • 该文件中定义的环境变量对所有的用户都有效
  • 2.当前用户的环境变量文件
    • 只对该用户有效
    • 用户的环境变量文件名称与用户的shell类型有关
      • 如果shell类型为bsh,则该文件包括.profile(最常使用的交互式的shell使用)和.bashrc(非交互式的shell使用)。

直接在命令行增加环境变量,只在本次会话中有效。会话一旦退出,该环境变量将会失效。

要永久增加某个环境变量,可以在.profile(对于bsh来说)中进行。语法如下:

export 环境变量名称=值

通过env变量可以查询当前定义的全部环境变量。

查询某个单独的环境变量:echo $环境变量名称

要删除某个环境变量的定义:unset $环境变量名称

除了通过执行shell命令获取环境变量外,还可以通过编程的方式获取。

Linux中有两个系统调用,getenv用于获取环境变量,putenv用于设置环境变量。

#include<stdilb.h>
char *getenv(__const char *__name);
int putenv(char *__string);
  • ___name: getenv输入参数,环境变量名称。
  • __string: putenv输入参数,要设置的环境变量串,“环境变量名称=值”。
  • getenv返回值
    • NULL:表明相关的环境变量未定义。
    • 其他:环境变量的值。
  • putenv返回值
    • -1:调用失败
    • 0:调用成功
/*编程实现设置环境变量CONFIG_PATH的值为/etc*/
#include<stdio.h>
#include<stdlib.h>
int main()
{
        char *buffer;
        buffer = getenv("CONFIG_PATH");
        if(buffer==NULL)
        {
                putenv("CONFIG_PATH=/etc");
        }
        printf("CONFIG_PATH=%s\n",getenv("CONFIG_PATH"));
        return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim putenv.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o putenv.out putenv.c
yang@yang-virtual-machine:~/桌面/c$ ./putenv.out
CONFIG_PATH=/etc
  • 首先检查是否定义,未定义则调用putenv设置,最后输出。

修改环境变量还可以通过系统调用setenv和unsetenv进行,其实现的功能与putenv类似。

2.2.3进程的内存分配
  • 编程过程中,根据程序的需要可能需要动态申请内存。

  • 程序中定义的局部变量是在进程的栈(stack)中分配空间的。其内存的分配和释放不需要用户关心,由系统自动完成。

  • 在程序运行时需要需要动态分配的内存,将从系统可用内存中申请新的空间,并加入到进程的堆(heap)。动态申请的内存在使用完毕后,应该由用户进行释放。

Linux提供了专门的用于内存申请及释放的系统调用,分别是申请内存的malloc、重新申请内存的realloc和释放内存的free。

#include<stdlib.h>
void *malloc(size_t __size);/*__size:输入参数,内存缓存区的大小,以字节为单位。*/
void *realloc(void *__ptr,size_t __size);/*__ptr:realloc的输入参数,已有的内存缓存区指针。*/
void free(void *__ptr);/*__ptr:free的输入参数,要释放的内存缓存区指针。*/

编程实现由键盘输入字符,保存于程序中动态分配的空间中。

#include<stdio.h>
#include<stdlib.h>
int main()
{
        int i;
        char c,*p;
        p = (char*)malloc(10);/*初次分配长度为10个字节的内存空间*/
        for(i=0;;i++)/*循环调用getchar从键盘获取输入数据,并保存至动态内存分配的空间中*/
        {
                c=getchar();/*从键盘读入单个字符数据*/
                if(i>9)/*如果输入数据的长度已经超过初始分配的空间,则调用realloc增加分配空间*/
                {
                        p=(char *)realloc(p,1);
                }
                if(c=='\n')/*输入回车符号,则中断输入退出循环。*/
                {
                        p[i]='\0';/*将最后一位字符设置为空字符,以便后续的字符串输出。*/
                        break;
                }
                else
                {
                        p[i]=c;/*将输入的字符保存到分配好的内存空间中。*/
                }
        }
        printf("%s\n",p);/*输出缓存区中的内容*/
        free(p);/*释放动态分配的内存*/
        return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim dtnc.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o dtnc.out dtnc.c
yang@yang-virtual-machine:~/桌面/c$ ./dtnc.out
1234567890abc
1234567890abc

2.3进程的创建

2.3.1调用fork创建进程

调用fork完成后,将生成新的进程。新生成的进程称为子进程,而原来的调用进程称为父进程。fork系统调用非常特殊,调用fork一次将返回两次,在父进程中返回子进程的进程标识符,在子进程中返回值为0。

  • 子进程继承了父进程大部分的属性:

    • 进程的实际用户ID、实际用户组ID和有效用户ID、有效用户组ID。

    • 进程组ID、会话ID及控制终端。

    • 当前工作目录及根目录。

    • 文件创建掩码UMASK。

    • 环境变量。

  • 不能继承的进程属性:

    • 进程号、子进程号不同于任何一个活动的进程组号。

    • 子进程的用户时间和系统时间,这两个时间被初始化为0.

    • 子进程的超时时钟设置为0,这个时钟是由alarm系统调用使用的。

    • 子进程的信号处理函数指针组置为空。原来的父进程中的信号处理函数都将失效。

    • 父进程的记录锁。

注意:

  • 父进程中已经打开的文件描述符可以在子进程中直接使用。
    • 这些描述符不仅包括文件描述符,而且包括其他如套接口描述符等。
    • 在这种情况下,这些描述符的引用计数已经加一(每fork一次就加一)。
    • 因此,在关闭这些描述符时,要记住多次关闭直至描述符的引用计数为0。
  • 子进程复制了父进程的数据段,包括全局变量,但是父子进程各有一份全局变量的拷贝。因此,不能通过全局变量在父子进程间通信,而要通过专门的进程间通信机制。
/*编程创建多个进程,每个进程输出当前时间*/
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    pid_t pid;/*进程ID*/
    signal(SIGCLD,SIG_IGN);/*信号处理,忽略SIGCLD信号,避免形成僵尸进程*/
    switch(pid=fork())/*创建子进程*/
    {
        case -1:/*创建失败*/
            perror("fork");/*输出错误信息*/
            break;
        case 0:/*子进程*/
            printf("子进程:进程ID=%d\n",getpid());/*输出当前进程的进程ID*/
            exit(0);
            break;
        default:/*父进程*/
            printf("父进程:创建子进程 %d 成功.\n",pid);/*输出新创建的子进程的进程ID*/
            sleep(5);/*休眠5秒*/
            break;
    }
    return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim fork.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o fork.out fork.c
yang@yang-virtual-machine:~/桌面/c$ ./fork.out
父进程:创建子进程 8786 成功.
子进程:进程ID=8786

提示:vfork系统调用的目的是创建子进程,与fork不同的是,vfork创建子进程的目的是调用exec,并且vfork产生的子进程与父进程共享大多数进程空间。也就是说,vfork调用后,父子进程共享数据段。

2.3.2调用exec系列函数执行程序
  • exec系统函数并不创建新进程,调用exec前后的进程ID是相同的。

  • exec系列函数的主要工作是清除父进程的可执行代码映像,用新程序的代码覆盖调用exec的进程代码。

  • exec执行成功,进程将从新程序的main函数入口开始执行。

  • 调用exec函数后,除了进程ID保持不变外,还有下列进程属性不变:

    • 进程的父进程ID。
    • 实际用户ID和实际用户组ID。
    • 进程组ID、会话ID和控制终端。
    • 定时器剩余时间。
    • 当前工作目录及根目录。
    • 文件创建掩码UMASK。
    • 进程的信号掩码。

exec系列函数共有6种不同的形式,统称为exec函数。分为两组:

  • execl系列:execl、execle、execlp
    • l是list(列表)的意思,表示execl系列函数需要将每个命令行参数作为函数的参数进行传递。
  • execv系列:execv、execve、execvp
    • v是vector(矢量)的意思,表示execv系列函数将所有函数包装到一个矢量数组中传递即可。
#include<unistd.h>
int execv(__const char *__path,char *__const __argv[]);
int execve(__const cahr *__path,char *__const __argv[],char *__const __envp[]);
int execvp(__const char *__file,char *__const __argv[]);
int execl(__const char *__path,__const char *__arg,...);
int execle(__const char *__path,__const char *__arg,...);
int execlp(__const char *__file,__const char *__arg,...);
  • 参数:
    • __path:输入参数,要执行的程序路径。(路径名,可以绝对路径或者相对路径)在execv、execve、execl、execle四个函数中,使用带路径名的文件名作为参数。
    • __file:输入参数,要执行的程序名称(指文件名)。如果该参数中包含“/”字符,则视为路径名直接执行;否则视为单独的文件名,系统将根据PATH环境变量指定是路径顺序搜索指定的文件。
    • __argv:输入参数,命令行参数的矢量数组。
    • __envp:输入参数,带有该参数的exec函数,可以在调用exec系列函数时,指定一个环境变量数组。其他不带该参数的exec系列函数,则使用调用进程的环境变量。
    • __arg:程序的第0个参数,即程序名本身,相当于__argv[0]
    • …:输入参数,命令行参数列表。调用相应程序时有多少命令行参数,就需要有多少个输入参数项。
      • 注意:在所有命令行参数的最后,应该增加一个空的参数项,表明命令行参数结束。
  • 返回值:
    • -1:调用失败
    • 无返回:调用成功。由于调用成功后,当前进程的代码空间被新进程覆盖,所以无返回。
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
    pid_t pid;/*进程标识变量*/
    char *para[]={"ls","-a",NULL};/*定义参数数组,为execv所使用*/
    if((pid=fork())<0){/*创建新的子进程*/
        perror("fork");/*错误处理*/
        exit(0);
    }
    if(pid==0){/*子进程*/
        if(execl("/bin/ls","ls","-l",(char *)0)==-1){/*执行ls -l命令*/
            perror("execl");/*错误处理*/
            exit(0);
        }
    }
    if((pid=fork())<0){/*创建新的子进程*/
        perror("fork");/*错误处理*/
        exit(0);
    }
    if(pid==0){/*子进程*/
        if(execv("/bin/ls",para)==-1){/*执行ls -a命令*/
            perror("execv");/*错误处理*/
            exit(0);
        }
    }
    return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim exec.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o exec.out exec.c
yang@yang-virtual-machine:~/桌面/c$ ./exec.out
yang@yang-virtual-machine:~/桌面/c$ .	   cuowuhao.c	 flock.c     jllswj.c	    scwj.c	test.c
..	   cuowuhao.cpp  flock.out   jllswj.out     scwj.out	test.debug
电话本.c   cuowuhao.out  fork.c      jlwj.c	    setsid.c	test.out
aa.out	   cuowu.out	 fork.out    jlwj.out	    setsid.out	test.sh
a.c	   dhb.out	 fsync.c     main.c	    shell1.sh	test.txt
a.cxx	   dkwj.c	 fsync.out   main.out	    shell2.sh	txt11.txt
a.debug    dkwj.out	 fzwj.c      mytemp-CJnkkD  sync.c	txt2.txt
a.o	   dqwj.c	 fzwj.out    mytemp-xGIbO7  sync.out	xjcz.c
a.out	   dqwj.out	 gbwj.c      putenv.c	    test1.cpp	xjcz.out
a.s	   dtnc.c	 gbwj.out    putenv.out     test1.out	xrwj.c
b.c	   dtnc.out	 getopt.c    qrz.c	    test1.sh	xrwj.out
cjml.c	   exec.c	 getopt.out  qrz.out	    test2.sh	ydwj.c
cjml.cpp   exec.out	 hello.txt   s1.txt	    test3.sh	ydwj.out
cjml.out   fcntl.c	 jcxx.c      scml.c	    test4.sh	ydwz.c
cuowu.cpp  fcntl.out	 jcxx.out    scml.out	    test5.sh	ydwz.out
总用量 792
-rw-rw-r-- 1 yang yang  3436  21 20:35 电话本.c
-rwxrwxr-x 1 yang yang 16024  126 22:19 aa.out
-rw-rw-r-- 1 yang yang   197  120 21:05 a.c
-rw-rw-r-- 1 yang yang 18067  123 23:59 a.cxx
-rwxrwxr-x 1 yang yang 17360  125 21:17 a.debug
-rw-rw-r-- 1 yang yang  1776  125 20:05 a.o
-rwxrwxr-x 1 yang yang 16024  126 22:19 a.out
-rw-rw-r-- 1 yang yang  1339  125 19:59 a.s
-rw-rw-r-- 1 yang yang   197  123 23:39 b.c
...#省略一部分
2.3.3调用system创建进程

为方便调用外部程序,Linux提供了system系统调用。

  • system将加载外部的可执行程序,执行完毕后返回调用进程。

  • system的返回码就是加载的外部可执行程序的返回码。

#include<stdlib.h>
int system(__const char *__command);
  • 参数:
    • __command:要加载的外部程序的文件名。
  • 返回值:
    • -1:执行system失败。
    • 127:执行失败。
      • 在system的内部实现中,system首先fork子进程,然后调用exec执行新的shell,在shell中执行要执行的程序。
      • 如果在调用exec时失败,system将返回127。
      • 由于要加载的外部程序也有可能返回127。在system返回127时,最好判断一下errno:errno不为0,表明调用system失败;否则,调用system成功,被加载的程序返回码是127。
    • 其他:执行system成功,返回值是调用的外部程序的返回码。
#include<stdio.h>
#include<stdlib.h>
int main()
{
        printf("call ls return %d\n",system("ls -l"));
        return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim sys.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o sys.out sys.c
yang@yang-virtual-machine:~/桌面/c$ ./sys.out
总用量 812
-rw-rw-r-- 1 yang yang  3436  21 20:35 电话本.c
-rwxrwxr-x 1 yang yang 16024  126 22:19 aa.out
-rw-rw-r-- 1 yang yang   197  120 21:05 a.c
-rw-rw-r-- 1 yang yang 18067  123 23:59 a.cxx
-rwxrwxr-x 1 yang yang 17360  125 21:17 a.debug
-rw-rw-r-- 1 yang yang  1776  125 20:05 a.o
-rwxrwxr-x 1 yang yang 16024  126 22:19 a.out
...
call ls return 0

2.4进程的终止

进程执行完毕后,应该合理的终止,释放进程占用的资源。

终止进程:

  • 接收到其他进程发送的信号而被动终止
  • 进程自己执行完毕后主动退出进程
2.4.1调用exit退出进程

进程要执行的功能执行完毕,或者执行过程中出错,需要调用exit退出进程。

在Linux中,除了调用exit可以结束进程外,_exit也可以实现类似功能。

但是,_exit函数在退出时并不刷新带缓冲I/O的缓冲区。所以,在使用带缓冲的I/O操作时,应该调用exit函数。

带缓冲的I/O是指进程对输入输出流进行了改进,提供了一个流缓,当用fwrite函数网磁盘写数据时,先把数据写入流缓冲区中,当达到一定条件,比如流缓冲区满了,或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲,再经块缓写入磁盘。

#include<stdlib.h>
void exit(int __status);
#include<unistd.h>
void _exit(int __status);
  • __status:输入参数,程序退出时的返回码。该返回码可以在shell中通过$?系统变量取得,也可以通过system系统调用的返回码取得,还可以在父进程中通过调用wait函数获得。
2.4.2调用wait等待进程退出

一个进程结束运行时,将向其父进程发送SIGCLD信号。父进程在收到SIGLCD信号后,可以忽略该信号或者安装信号处理函数处理该信号。

  • 处理该信号需要调用wait系列函数。
  • wait系列函数的作用:等待子进程的退出,并获取子进程的返回码。

通常情况下,父进程调用wait等待其子进程的退出。如果没有任何子进程退出,则wait在缺省状态下将进入阻塞状态,直到调用进程的某个子进程退出。

缺省,即系统默认状态,意思与“默认”相同。 缺省是一种计算机术语,指在无决策者干预情况下,对于决策或应用软件、计算机程序的系统参数的自动选择。 默认选项的设计可以在用户不须决策的状况下就可以基础地使用上述的软件与程序。

所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

#include<sys/wait.h>
__pid_t wait(__WAIT_STATUS __stat_loc);
__oid_t waitpid(__pid_t __pid,int *__stat_loc,int __options);
  • 参数说明:

    • __stat_loc:输出参数,用于保存子进程的结束状态。
    • __pid:输入参数,用于waitpid。该参数可以有若干输入方式,每种方式有其独特的含义。
    • __options:输入参数,用于waitpid。该参数指定了调用waitpid时的选项。
  • Linux提供了多个宏以便从该结束状态中获取特定的信息,具体信息:

    • WIFEXITED(__stat_loc):子进程正常结束时,为非0值

    • WEXITSTATUS(__stat_loc):取得子进程exit()返回的结束代码

      • 通常情况下,应先用WIFEXITED判断是否正常结束才能使用此宏。
    • WIFSIGNALED(__stat_loc):如果子进程是因为信号而结束则返回真

    • WTERMSIG(__stat_loc):返回子进程因信号而中止的信号代码

      • 通常应先用WIFSIGNALED判断后才使用此宏。
    • WIFSTOPPED(__stat_loc):如果子进程处于暂停执行情况则此宏返回真

      • 只有使用WUNTRACED选项才会有此情况。
    • WSTOPSIG(__stat_loc):返回引发子进程暂停的信号代码

      • 通常应先用WIFSTOPPED来判断后才使用此宏。
  • 返回值:

    • -1失败;其他成功,返回值为退出的子进程ID。
/*编写代码实现子进程退出*/
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>
void handle_sigcld(int signo)/*SIGCLD信号处理函数*/
{
    pid_t pid;/*保存退出进程的进程ID*/
    int status;/*保存进程的退出状态*/
    if((pid=wait(&status))!=-1)/*调用wait等待子进程退出*/
    {
        printf("子进程%d 退出\n",pid);/*输出提示信息*/
    }
    if(WIFEXITED(status))/*判断子进程退出时是否有返回码*/
    {
        printf("子进程返回%d\n",WEXITSTATUS(status));/*输出子进程的返回码*/
    }
    if(WIFSIGNALED(status))/*判断子进程是否被信号中断而结束*/
    {
        printf("子进程被信号%d 结束\n",WTERMSIG(status));/*输出中断子进程的信号*/
    }
}
int main()
{
    pid_t pid;/*定义pid_t类型变量,用于保存进程ID*/
    signal(SIGCLD,handle_sigcld);/*安装SIGCLD信号*/
    if((pid=fork())<0)/*创建新进程*/
    {
        perror("fork");
        exit(0);
    }
    if(pid==0){
        exit(123);/*子进程返回123*/
    }
    sleep(5);/*父进程休眠5秒,等待子进程退出*/
    return 0;
}
yang@yang-virtual-machine:~/桌面/c$ vim wait.c
yang@yang-virtual-machine:~/桌面/c$ gcc -o wait.out wait.c
yang@yang-virtual-machine:~/桌面/c$ ./wait.out
子进程18358 退出
子进程返回123

3. Shell入门(超算习堂)

初识shell

vim hello.sh
chmod +x ./hello.sh
./hello.sh
/bin/sh hello.sh
#!/bin/bash
echo "Hello World!"

#!:是一个标记,用处是告诉系统这个脚本需要使用什么解释器来执行,这里是bash。

Shell基本语法

shell的变量
#!/bin/bash
ehpc="easy_hpc"
echo $ehpc
vim test.sh
bash test.sh
#结果:easy_hpc
shell的运算符
#!/bin/bash
a=5
b=10

val=`expr $a + $b`
echo "a + b : $val"

val=`expr $a - $b`
echo "a - b : $val"

val=`expr $a \* $b`
echo "a * b : $val"

val=`expr $a / $b`
echo "a / b : $val"

val=`expr $a % $b`
echo "a % b : $val"
yang@yang-virtual-machine:~/桌面$ vim test2.sh
yang@yang-virtual-machine:~/桌面$ bash test2.sh
a + b : 15
a - b : -5
a * b : 50
a / b : 0
a % b : 5
shell的输出命令

PHP中也有echo命令

echo命令:

  • 显示普通的字符:echo "ehpc is easy hpc"

  • 显示转义字符:echo "\"ehpc is easy hpc\""

  • 显示命令执行结果:

    echo `date` #显示当前时间
    

另一个输出命令printf

printf 命令的语法:

printf  format-string  [arguments...]

参数说明:

  • format-string: 为格式控制字符串
  • arguments: 为参数列表。
#!/bin/bash
 
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg  
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543
printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876
姓名     性别   体重kg
郭靖     男      66.12
杨过     男      48.65
郭芙     女      47.99

%s %c %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出。

%-10s 指一个宽度为 10 个字符(- 表示左对齐,没有则表示右对齐),任何字符都会被显示在 10 个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。

%-4.2f 指格式化为小数,其中 .2 指保留2位小数。

#!/bin/bash
 
# format-string为双引号
printf "%d %s\n" 1 "abc"

# 单引号与双引号效果一样
printf '%d %s\n' 1 "abc"

# 没有引号也可以输出
printf %s abcdef

# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
printf %s abc def

printf "%s\n" abc def

printf "%s %s %s\n" a b c d e f g h i j

# 如果没有 arguments,那么 %s 用NULL代替,%d 用 0 代替
printf "%s and %d \n"
1 abc
1 abc
abcdefabcdefabc
def
a b c
d e f
g h i
j  
 and 0
shell数组的使用
yang@yang-virtual-machine:~/桌面$ vim test3.sh
yang@yang-virtual-machine:~/桌面$ bash test3.sh
first element: E
second element: H
third element: P
fourth element: C
#!/bin/bash
array_test=(E H P C)#大写字母之间加了空格
echo "first element: ${array_test[0]}"
echo "second element: ${array_test[1]}"
echo "third element: ${array_test[2]}"
echo "fourth element: ${array_test[3]}"

shell之流程控制

if else

格式:

if condition
then
	command
else
	command
fi
while

格式:

while condition
do
	command
done
for

格式:

for var in item1 item2 ...itemN
do
	command
done
until

格式:

until condition
do
	command
done

Shell编程之函数使用

定义和调用函数

定义格式:

function_name ()
{
	statement
}
#或者
function function_name ()
{
	statement
}

调用函数的格式:

function_name parm1 parm2...

例子:

#!/bin/bash
function echo_hello()
{
	echo "hello world"
}
echo_hello
函数参数
#!/bin/bash
test(){
	echo " $1 !"
	echo " $2 !"
	echo " $5 !"
	echo " $10 !"
	echo " ${10} !"
}
test 3 5 7 9 11 13 15  17 19 21
yang@yang-virtual-machine:~/桌面$ vim test4.sh
yang@yang-virtual-machine:~/桌面$ bash test4.sh
 3 !
 5 !
 11 !
 30 !
 21 !

shell规定,当n>=10时,需要使用${n}来获取参数。

Shell输入输出重定向

输出重定向

输出重定向:command > file_name(文件中内容会被新内容代替)

使用>>,将新的内容添加到文件末尾

yang@yang-virtual-machine:~/桌面$ vim test5.sh
yang@yang-virtual-machine:~/桌面$ date > test.txt
yang@yang-virtual-machine:~/桌面$ cat test.txt
2023年 02月 03日 星期五 19:29:02 CST
yang@yang-virtual-machine:~/桌面$ date >> test.txt
yang@yang-virtual-machine:~/桌面$ cat test.txt
2023年 02月 03日 星期五 19:29:02 CST
2023年 02月 03日 星期五 19:30:17 CST
输入重定向

同输出重定向一样, unix命令也可以从文件获取输入,语法:command1 < file1

yang@yang-virtual-machine:~/桌面$ wc -l < test.txt
2
yang@yang-virtual-machine:~/桌面$ wc -l test.txt
2 test.txt
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值