什么是进程?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/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;
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进程作为自己的父进程。