有一些函数可以返回进程的标识符
#include
#include
pid_t getpid(void); //返回调用进程的进程ID
pid_t getppid(void); //返回调用进程的父进程
uid_t getuid(void); //返回调用进程的实际用户ID
uid_t geteuid(void); //返回调用进程的有效用户ID
gid_t getgid(void); //返回调用进程的实际组ID
git_t getegid(void); //返回调用进程的有效组ID
常见的状态字符有:
STAT字符 | 说明 |
---|---|
S | 睡眠。通常是在等待某个事件的发生 |
R | 运行/可运行,即在运行队列中,处于正在运行或即将运行状态 |
D | 不可中断的睡眠(等待,不响应异步信号)。通常是在等待输入或输出完成 |
T | 停止 |
Z | 僵尸进程 |
N | 低优先级任务 |
s | 进程是回话期首进程 |
+ | 进程属于前台进程组 |
l | 进程是多线程的 |
< | 高优先级任务 |
在进程中执行另一个程序的一个简单方法是调用标准库函数system,原型如下:
#include
int system(const char *command);
解释:system函数运行command命令并等待该命令完成,
本质是(执行“/bin/sh -c command”)。system函数调用成功时返回相应命令的退出状态码,如果无法启动shell则返回127,发生其它错误时返回-1
注意:使用system函数并非启动其它进程的理想手段,因其必须启动一个shell,再使用shell执行相应的命令。下面有一个小例子:
//command.c
#include
int main(int argc, char **argv)
{
printf("这条命令是由调用system函数执行的。\n");
return 0;
}
将该段代码编译成command可执行文件。
//system.c
#include
#include
int main(int argc, char **argv)
{
printf("接下来将要调用system函数来执行一个名为command的命令。\n");
system("./command");
printf("Done.\n");
return 0;
}
编译运行
biantiao@lazybone1994-ThinkPad-E430:~/Linux$ gcc -o system system.c
biantiao@lazybone1994-ThinkPad-E430:~/Linux$ ./system
接下来将要调用system函数来执行一个名为command的命令。
这条命令是由调用system函数执行的。
Done.
biantiao@lazybone1994-ThinkPad-E430:~/Linux$
调用exec系列函数可以执行另外一个程序。这些函数的原型如下:
#include
extern char **evniron;
int execl(const char *path, const char *arg, ...);
int execlp(cosnt char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, const char *argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[]);
int execve(cosnt char *path, char *const argv[], char *const envp[]);
解释:当一个进程调用一种exec函数时,该进程将完全由新程序替换当前进程的正文,数据,堆和栈段,所以调用exec前后进程ID并未改变。
exec系列函数各参数和返回值的含义如下
- path:待运行的程序全路径名
- file:待运行的程序名,通过PATH环境变量搜索其路径
- arg:命令参数
- …:可选的一到多个命令参数,要求最后一个必须是NULL
- argv:命令参数指针数组
- envp:传递给待运行程序的环境变量指针数组
- 返回值:成功时不返回,出错时返回-1
这几个函数之间的区别
函数名包含字母p(表示path)的execlp, execvp和execvpe函数取文件名file作为第一个参数,其它函数则取路径名path作为第一个参数。当指定file作为参数时:如果file中包含/,则就将其视为路径名,否则就按PATH环境变量的设定,在相关目录中搜寻可执行文件。
函数名字中包含字母l的execl,execlp和execle要求将新程序的每个命令行参数都作为一个单独的参数,然后在最后一个参数后附加一个空指针参数结尾。
函数名中包含字母v的另外三个函数execv,execvp和execve则要求先构造一个指向各参数的指针数组,数组的最后一个元素也必须是空指针,然后以该数组地址作为这三个函数的参数。
函数以字母e结尾的三个函数execle,execvpe和execve可以传递一个指向环境字符串指针数组的指针,该指针数组也必须以空指针作为最后一个元素
一个现存进程创建一个新进程的唯一方法是调用fork或vfork函数
#include
pid_t fork(void);
由fork创建的新进程被称为子进程。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值是子进程的PID。
在调用fork之后,子进程和父进程继续执行fork之后的指令。
一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,父进程和子进程是完全独立运行的。如果父,子进程之间相互同步,则要求采用某种形式的进程之间的通信机制。
子进程是父进程的复制品。例如:子进程获得父进程数据空间,堆和栈的复制品。注意,这是子进程所拥有的拷贝,父子进程并不共享这些储存空间。如果正文段是只读的,则父,子进程共享正文段。
Linux下的fork函数并不对父进程的数据段,堆和栈进行完全拷贝,而是使用了写时复制的技术,让父,子进程共享这些区域,而且内核将它们的存取许可权变为只读的。当有进程试图修改这些区域时,才由内核为有关部分做一个拷贝。
fork函数有以下两种用法
- 一个父进程希望复制自己,使父,子进程执行不同的代码段。这在网络服务进程中是常见的——父进程等待委托者的服务请求,当请求达到时,父进程调用fork,使子进程处理此请求,父进程则继续等待下一个服务请求。
- 一个进程要执行一个不同的程序,这对shell是常见的。在这种情况下,子进程在从fork返回后立即调用exec。当然了,子进程在fork和exec之间可以更改自己的属性。
vfork函数与fork函数最大的一个区别是,vfork函数创建子进程后会阻塞父进程,其原型如下:
#include
#include
pid_t vfork(void);
vfork和fork一样都创建一个子进程,但是它并不会将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec或exit,于是也就不会访问该地址空间。但在子进程调用exec或exit之前,它在父进程的空间中运行。
vfork和fork之间的另一个区别是:vfork保证子进程先运行,在子进程调用exec或exit之后父进程才可能被调度运行
#include
#include
#include
#include
int main(void)
{
pid_t pid; /* 用于保存PID */
char *message; /* 用于保存消息字符串 */
int n = 2; /* 技术变量 */
printf("fork program starting\n");
pid = vfork(); /* 创建子进程 */
switch (pid){
case -1:
perror("fork failed.\n");
exit(1);
case 0:
message = "This is the child.";
n = 5;
break;
default:
message = "This is the parent.";
n++;
break;
}
for (; n > 0; n--){
puts(message);
sleep(1);
}
exit(0);
}
对于正常终止的情况,传向exit或 _exit的参数,或main函数的返回值,指明了它们的退出状态,内核以该“退出状态”作为进程的“终止状态”。在异常终止的情况下,内核(不是进程本身)会产生一个指示其异常终止原因的终止状态(termination status)。终止进程的父进程可使用wait或waitpid函数取得其终止状态。
如果父进程在子进程之前终止,则它们的父进程都将改为init进程,这种处理方法保证每一个进程都有父进程。
如果子进程在父进程之前终止,那么父进程如何得到子进程的终止状态呢?答案是,当进程终止的时候,内核并未马上释放其进程控制块(PCB),而是在其中保留了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括进程ID,该进程的终止状态,以及该进程使用的CPU时间总量。
一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它扔占用的资源)的进程被称为僵尸进程(zombie)
#include
#include
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait函数等待任一子进程的结束,waitpid函数则可等待指定的子进程的结束。
在父进程中调用wait或者waitpid可能发生如下情况:
- 阻塞(如果其所有子进程都还在运行)
- 带子进程的终止状态立即返回
- 出错立即返回(如果它没有子进程)
当一个进程终止时,内核会向其父进程发送SIGCHLD信号。父进程默认处理是忽略该信号,但也可以设置一个信号发生时即被调用的回调函数以捕获该信号。如果父进程在捕获SIGCHLD信号的回调函数中调用wait,则可期望wait会立即返回。但是在一个任意时刻调用wait,则进程可能会阻塞