《linux程序设计学习笔记》之----进程

什么是进程?UNIX标准将其定义为:一个其中运行着一个或者多个线程的地址空间和这些线程所需要的系统资源。或者说,每个运行着的程序实例就是一个进程。正在运行的程序或进程有程序代码、数据、变量(占用着系统内存)、打开的文件(文件描述符)和环境组成。一般来说,linux系统会在进程之间共享程序代码和系统函数库,所以在任何时刻内存中都只有代码的一份副本。

每个进程都会被分配一个唯一的数字编号,我们称之为进程标识符或pid。它通常是一个取值范围从2到32768的正整数。当进程被启动时,系统将安顺序选择下一个未被使用的数字作为的pid,当数字绕回一圈时,新的pid重新从2开始。数字1一般是为特殊进程init保留的,init进程负责管理其他进程。

例如,当我们执行2个shell命令find 123.和find 234.txt时,会产生2个进程。这2个进程共享的部分有:find程序代码,以及c语言函数库,不同点就是传入的参数。进程都有自己的栈空间,用于保存函数中局部变量和控制函数的调用与返回。进程还有自己的环境空间,包含专门为这个进程建立的环境变量,这些信息都可以在目录/proc中看到。

在linux中有一张进程表来保存进程的各种信息。它类似与一个数据结构,把当前加载在内存中的所有进程的有关信息保存在一个表中,其中包含进程的pid、进程的状态。命令字符串和其他一些ps命令输出的各类信息。

1.查看进程

ps命令可以显示我正在运行的进程、其他用户正在运行的进程或者目前在系统上运行的所以进程。例:

[aaa@localhost ~]$ ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 Jun18 ?        00:00:02 /sbin/init
root          2      0  0 Jun18 ?        00:00:00 [kthreadd]
root          3      2  0 Jun18 ?        00:00:00 [migration/0]
root          4      2  0 Jun18 ?        00:00:00 [ksoftirqd/0]
root          5      2  0 Jun18 ?        00:00:00 [stopper/0]
root          6      2  0 Jun18 ?        00:00:00 [watchdog/0]
root          7      2  0 Jun18 ?        00:00:00 [migration/1]
root          8      2  0 Jun18 ?        00:00:00 [stopper/1]
root          9      2  0 Jun18 ?        00:00:00 [ksoftirqd/1]
root         10      2  0 Jun18 ?        00:00:00 [watchdog/1]
root         11      2  0 Jun18 ?        00:00:00 [migration/2]
root         12      2  0 Jun18 ?        00:00:00 [stopper/2]
root         13      2  0 Jun18 ?        00:00:00 [ksoftirqd/2]
root         14      2  0 Jun18 ?        00:00:00 [watchdog/2]
root         15      2  0 Jun18 ?        00:00:00 [migration/3]
root         16      2  0 Jun18 ?        00:00:00 [stopper/3]
root         17      2  0 Jun18 ?        00:00:00 [ksoftirqd/3]
root         18      2  0 Jun18 ?        00:00:00 [watchdog/3]

默认情况下ps程序只显示与终端、主控台、串行口或伪终端保持连接的进程的信息。还有一些在运行不需要通过终端与用户进行通讯,他们通常是一些系统进程,linux用他们来管理共享资源。ps -a可查看所有进程,-f选项可显示完整进程信息。

[aaa@localhost ~]$ ps ax
   PID TTY      STAT   TIME COMMAND
     1 ?        Ss     0:02 /sbin/init
     2 ?        S      0:00 [kthreadd]
     3 ?        S      0:00 [migration/0]
     4 ?        S      0:00 [ksoftirqd/0]
     5 ?        S      0:00 [stopper/0]
     6 ?        S      0:00 [watchdog/0]
     7 ?        S      0:00 [migration/1]
     8 ?        S      0:00 [stopper/1]
     9 ?        S      0:00 [ksoftirqd/1]
    10 ?        S      0:00 [watchdog/1]
    11 ?        S      0:00 [migration/2]
    12 ?        S      0:00 [stopper/2]
    13 ?        S      0:00 [ksoftirqd/2]
    14 ?        S      0:00 [watchdog/2]
    15 ?        S      0:00 [migration/3]
    16 ?        S      0:00 [stopper/3]
    17 ?        S      0:00 [ksoftirqd/3]

STAT代表进程的状态信息,一些常用的状态如下:

S:睡眠,通常是在等待一个信号或有输入可用

R:运行。严格来说 是可运行,即在运行队列中,处在正在执行或者即将运行状态

D:等待,在等待输入或输出的完成。

T:停止

Z:死进程或者僵尸进程

N:低优先级任务,nice


一般而言,每个进程都是由另外一个我们称之为父进程的进程启动的,被父进程启动的进程叫做子进程。init进程是所有进程的祖先进程或者父进程,是系统第一个启动的进程。你可以认为init进程是系统的进程管理器。

2.启动新进程

在一个程序内启动另外一个程序,从而创建一个新的进程。

a.函数system()

库函数system()便可以实现这一功能。

#include<system>

int system(const char *string);

system函数,运行以字符串参数的形式传递给他的命令并等待该命令的完成,如同在shell中执行命令一般。

若shell无法启动,将返回127;若是其他错误,将返回-1;负责将返回该命令的返回码。

一个简单的例子:

#include<stdlib.h>
#include<stdio.h>

int main()
{
    printf("running ps with system\n");
    system("ps ax");
    printf("done.\n");
    exit(0);
}

编译通过之后运行:

[aaa@localhost libFile]$ ./system1 
running ps with system
   PID TTY      STAT   TIME COMMAND
     1 ?        Ss     0:01 /sbin/init
     2 ?        S      0:00 [kthreadd]
     3 ?        S      0:00 [migration/0]
     4 ?        S      0:00 [ksoftirqd/0]
     5 ?        S      0:00 [stopper/0]
     6 ?        S      0:00 [watchdog/0]
     7 ?        S      0:00 [migration/1]
     8 ?        S      0:00 [stopper/1]
     9 ?        S      0:00 [ksoftirqd/1]
    10 ?        S      0:00 [watchdog/1]
    11 ?        S      0:00 [migration/2]
    12 ?        S      0:00 [stopper/2]

system函数的局限性在于启动程序之前我们必须启动一个shell,针对于shell的安装和环境的依赖性较大。

另外一种启动新进程的方法是底层调用exec系列函数。

b.exec函数

exec函数可以吧当前进程替换为一个新的进程,新的进程由path或file参数指定。

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

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

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

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

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

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

这些函数都是由execve演化而来。我们使用其中一个函数执行与system相同的命令:

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>

int main()
{
    printf("running ps with system\n");
    execlp("ps","ps", "ax", 0);
    printf("done.\n");
    exit(0);
}

编译后运行:

[aaa@localhost libFile]$ ./execlp 
running ps with system
   PID TTY      STAT   TIME COMMAND
     1 ?        Ss     0:02 /sbin/init
     2 ?        S      0:00 [kthreadd]
     3 ?        S      0:00 [migration/0]
     4 ?        S      0:00 [ksoftirqd/0]
     5 ?        S      0:00 [stopper/0]
     6 ?        S      0:00 [watchdog/0]
     7 ?        S      0:00 [migration/1]
     8 ?        S      0:00 [stopper/1]
     9 ?        S      0:00 [ksoftirqd/1]
    10 ?        S      0:00 [watchdog/1]
    11 ?        S      0:00 [migration/2]
    12 ?        S      0:00 [stopper/2]
    13 ?        S      0:00 [ksoftirqd/2]

...

done并没有被输出,那是因为原程序已经被新程序ps ax替换掉了,除非exec函数发生了错误,这时它会返回-1。

值得注意的是:由exec启动的新进程继承了许多原进程的特性,特别的,在原进程中已打开的文件描述符在新进程中仍保持打开,除非它们“执行时关闭标志被置位”。任何在原进程中已打开的目录流都将在新进程中被关闭。

3.fork()调用

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
我们可以通过调用fork()创建一个新的进程(事实上也是子进程)。这个系统调用相当于复制当前进程,在进程表中创建一个新的表项,新表项中的许多属性与当前进程的相同的。新进程几乎与原进程一模一样,执行的代码也完全相同,但新进程有自己的数据空间、环境和文件描述符。fork和exec函数结合在一起使用就是创建新进程所需要的一切了。
fork调用返回的是新的子进程的pid,子进程中fork调用返回的是0.我们可以通过这一点来判断究竟谁是父进程,谁是子进程。fork失败,它将返回-1。
fork.cpp:
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>


int main()
{
    pid_t pid;
    char *message;
    int n;
    int exit_code;


    printf("fork program starting \n");
    pid = fork();
    switch(pid)
    {
    case -1:
        perror("fork failed");
        exit(1);
    case 0:
        message = "this is the child";
        n = 5;
exit_code = 37;

        break;
    default:
        message = "this is the parent";
        n = 3;
        exit_code =0;
        break;
    }


    for(;n>0;n--){
        puts(message);
        sleep(1);
    }


    if (pid != 0){
        int stat_val;
        pid_t child_pid;


        child_pid = wait(&stat_val);


        printf("child has finished :pid = %d\n",child_pid);

if (WIFEXITED(stat_val))
            printf("child exited with code %d\n",WEXITSTATUS(stat_val));
        else
            printf("child terminated abnomally\n");


    }
    exit(exit_code);
}

运行./fork:

[aaa@localhost libFile]$ ./fork
fork program starting 
this is the parent
this is the child
this is the parent
this is the child
this is the child
this is the parent
this is the child
this is the child
child has finished :pid = 7161
child exited with code 37

wait系统调用将暂停父进程直到它的子进程结束为止。这个调用返回子进程(通常已结束的子进程)的pid。

如果子进程率先结束,那么子进程就会成为僵尸进程直到父进程结束。若父进程异常结束,子进程自动把pid为1的init进程作为自己的父进程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值