您的位置:首页 > 其它

uc笔记07---进程管理,PID,#ps,getxxxid,fork,vfork,system

2015-09-30 17:00 423 查看
1. 进程与程序

1) 进程就是运行中的程序。一个运行着的程序,

可能有多个进程。进程在操作系统中执行特定的任务。

2) 程序是存储在磁盘上,包含可执行机器指令和数据的静态实体。

进程或者任务是处于活动状态的计算机程序。

2. 进程的分类

1) 进程一般分为 交互进程、批处理进程 和 守护进程三类。

2) 守护进程总是活跃的,一般是后台运行。

守护进程一般是由系统在开机时通过脚本自动激活启动,或者由超级用户 root 来启动。

3. 查看进程

1) 简单形式

# ps

以简略方式显示当前用户有控制终端的进程信息。

2) BSD 风格常用选项

# ps axu

a - 所有用户有控制终端的进程

x - 包括无控制终端的进程

u - 以详尽方式显示

w - 以更大列宽显示

3) SVR4 风格常用选项

# ps -efl

# ps -efl | grep 10086 // 查看具体某一个进程

# ps -efl | grep a.out

这里 | 是管道符,意思是把前者的输出当作后者的输入;

-e或-A - 所有用户的进程

-a - 当前终端的进程

-u 用户名或用户ID - 特定用户的进程 // #ps -u root

-g 组名或组ID - 特定组的进程 // #ps -g tarena

-f - 按完整格式显示

-F - 按更完整格式显示

-l - 按长格式显示

4) 进程信息列表

USER/UID: 进程属主。

PID: 进程 ID。

%CPU/C: CPU 使用率。

%MEM: 内存使用率。

VSZ: 占用虚拟内存大小(KB)。

RSS: 占用物理内存大小(KB)。

TTY: 终端次设备号,“?”表示无控制终端,如后台进程。

pts/1:虚拟终端,tty3:实体终端;

STAT/S: 进程状态。可取如下值:

O - 就绪。等待被调度。

R - 运行。Linux 下没有 O 状态,就绪状态也用 R 表示。

S - 可唤醒睡眠。系统中断,获得资源,收到信号,都可被唤醒,转入运行状态。

D - 不可唤醒睡眠。只能被 wake_up 系统调用唤醒。

T - 暂停。收到 SIGSTOP 信号转入暂停状态,收到 SIGCONT 信号转入运行状态。

W - 等待内存分页 (2.6 内核以后被废弃)。

X - 死亡。不可见。

Z - 僵尸。已停止运行,但其父进程尚未获取其状态。

< - 高优先级。

N - 低优先级。

L - 有被锁到内存中的分页。实时进程和定制 IO。

s - 会话首进程。

l - 多线程化的进程。

+ - 在前台进程组中。

START/STIME: 进程开始时间。

TIME: 进程运行时间。

COMMAND/CMD: 进程指令。

F: 进程标志。可由下列值取和:

1 - 通过 fork 产生但是没有 exec。

4 - 拥有超级用户特权。

PPID: 父进程 ID。

#ps -f

NI: 进程 nice 值,-20 到 19,可通过系统调用或命令修改。

PRI: 进程优先级。

#ps -lF

静态优先级 = 80 + nice,60 到 99,值越小优先级越高。

内核在静态优先级的基础上,根据进程的交互性计算得到实际(动态)优先级,

以体现对 IO 消耗型进程的奖励,和对处理器消耗型进程的惩罚。

ADDR: 内核进程的内存地址。普通进程显示“-”。

SZ: 占用虚拟内存页数。

WCHAN: 进程正在等待的内核函数或事件。

PSR: 进程被绑定到哪个处理器。

例如:

# ps axu

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND

root 1 0.0 0.0 2172 668 ? Ss 09:03 0:00 init [5]

root 2 0.0 0.0 0 0 ? S< 09:03 0:00 [migration/0]

root 3 0.0 0.0 0 0 ? SN 09:03 0:00 [ksoftirqd/0]

root 4 0.0 0.0 0 0 tty4 S< 09:03 0:00 [watchdog/0]

root 5 0.0 0.0 0 0 pts/1 S< 09:03 0:00 [events/0]

. . .

# ps -l

F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD

0 S 500 3223 3217 0 75 0 - 1512 wait pts/1 00:00:00 bash

0 R 500 4561 3223 0 77 0 - 1356 - pts/1 00:00:00 ps

4. 父进程、子进程、孤儿进程和僵尸进程

内核进程(0)

init(1)

xinetd

in.telnetd <- 用户登录

login

bash

vi

1) 父进程启动子进程后,

子进程在操作系统的调度下与其父进程同时运行。

2) 子进程先于父进程结束,

子进程向父进程发送 SIGCHLD(17) 信号,父进程回收子进程的相关资源。

如果父进程没有回收子进程的相关资源,该子进程即成为僵尸进程。

3) 父进程先于子进程结束,子进程成为孤儿进程,

同时被 init 进程收养,即成为 init 进程的子进程。

tips:孤儿进程和僵尸进程不可能同时存在一个进程上;

5. 进程标识符(进程 ID)

1) 每个进程都有一个以非负整数表示的唯一标识,即进程 ID/PID。

2) 进程 ID 在任何时刻都是唯一的,但可以重用,当一个进程退出时,其进程 ID 就可以被其它进程使用。

3) 延迟重用。

范例:delay.c

#include <stdio.h>

int main (void) {

printf ("进程ID:%u\n", getpid ());

return 0;

}

运行测试:

#a.out

进程ID:16431

#a.out

进程ID:16432

#a.out

进程ID:16433

#a.out

进程ID:16431

6. getxxxid

#include <unistd.h>

getpid - 获取进程ID

getppid - 获取父进程ID

getuid - 获取实际用户ID(登录用户 ID)

geteuid - 获取有效用户ID(属主 ID)

getgid - 获取实际组ID

getegid - 获取有效组ID

范例:id.c

#include <stdio.h>

int main (void) {

printf (" 进程ID:%u\n", getpid ());

printf (" 父进程ID:%u\n", getppid ());

printf ("实际用户ID:%u\n", getuid ());

printf ("有效用户ID:%u\n", geteuid ());

printf (" 实际组ID:%u\n", getgid ());

printf (" 有效组ID:%u\n", getegid ());

return 0;

}

以其它用户身份登录并执行

# a.out

输出

进程ID:16443

父进程ID:15890

实际用户ID:1000 - 实际用户 ID 取父进程(shell)的实际用户 ID

有效用户ID:1000 - 有效用户 ID 取实际用户 ID

实际组ID:1000 - 实际组 ID 取父进程(shell)的实际组 ID

有效组ID:1000 - 有效组 ID 取实际组 ID

查看进程 ID:

#ps -eaf | grep 15890

执行

# ls -l a.out

输出

-rwxr-xr-x ...

^ ^

为a.out的文件权限添加设置用户ID位和设置组ID位

# chmod u+s a.out

# chmod g+s a.out

执行

# ls -l a.out

输出

-rwsr-sr-x ...

^ ^

以其它用户身份登录并执行

# a.out

输出

进程ID:...

父进程ID:...

实际用户ID:1000 - 实际用户 ID 取父进程(shell)的实际用户 ID

有效用户ID:0 - 有效用户 ID 取程序文件的属主 ID

实际组ID:1000 - 实际组 ID 取父进程(shell)的实际组 ID

有效组ID:0 - 有效组 ID 取程序文件的属组 ID

进程的访问权限由其有效用户 ID 和有效组 ID 决定。

通过此方法可以使进程获得比登录用户更高的权限。

比如通过 passwd 命令修改登录口令:

执行

ls -l /etc/passwd

输出

-rw-r--r--. 1 root root 1648 Nov 9 14:05 /etc/passwd

^

该文件中存放所有用户的口令信息,仅 root 用户可写,

但事实上任何用户都可以修改自己的登录口令,

即任何用户都可以通过 /usr/bin/passwd 程序写该文件:

执行

# ls -l /usr/bin/passwd

输出

-rwsr-xr-x. 1 root root 28816 Feb 8 2011 /usr/bin/passwd

^ ^

该程序具有设置用户 ID 位,且其属主为 root。

因此以任何用户登录系统,执行 passwd 命令所启动的进程,

其有效用户 ID 均为 root,对 /etc/passwd 文件有写权限。

7. fork

#include <unistd.h>

pid_t fork (void);

1)创建一个子进程,失败返回 -1。

2)调用一次,返回两次;在父进程中返回子进程的 PID;在子进程中返回 0;

利用返回值的不同,可以分别为父/子进程编写不同的处理分支。

范例:fork.c

#include <stdio.h>

#include <unistd.h>

int main (void) {

printf ("%u进程:我要调用fork()了...\n", getpid ());

pid_t pid = fork ();

if (pid == -1) {

perror ("fork");

return -1;

}

if (pid == 0) { // fork() 在子进程里返回 0;

printf ("%u进程:我是%u进程的子进程。\n", getpid (),

getppid ());

return 0;

}

printf ("%u进程:我是%u进程的父进程。\n", getpid (), pid);

sleep (1);

return 0;

}

3)子进程是父进程的副本,

子进程获得父进程数据段和堆栈段(包括 I/O 流缓冲区)的拷贝,

但子进程共享父进程的代码段。

范例:mem.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

int global = 100;

int main (void) {

int local = 200;

char* heap = (char*)malloc (256 * sizeof (char));

sprintf (heap, "ABC");

printf ("父进程:%d %d %s\n", global, local, heap);

pid_t pid = fork ();

if (pid == -1) {

perror ("fork");

return -1;

}

// 在子进程里对三个数据作修改

if (pid == 0) {

global++;

local++;

sprintf (heap, "XYZ");

printf ("子进程:%d %d %s\n", global, local, heap);

free (heap);

return 0;

}

sleep (1);

printf ("父进程:%d %d %s\n", global, local, heap);

free (heap);

return 0;

}

输出结果:

父进程:100 200 ABC

子进程:101 201 XYZ

父进程:100 200 ABC

范例:os.c

#include <stdio.h>

#include <unistd.h>

int main (void) {

printf ("ABC"); // 放到缓冲区

// 开始分支

pid_t pid = fork ();

if (pid == -1) {

perror ("fork");

return -1;

}

if (pid == 0) {

printf ("XYZ\n");

return 0;

}

sleep (1);

printf ("\n");

return 0;

}

输出:

ABCXYZ // 子进程输出,把父进程的缓冲区复制过来;

ABC // 父进程输出;

范例:is.c

#include <stdio.h>

#include <unistd.h>

int main (void) {

printf ("父进程:");

int a, b, c;

scanf ("%d%d%d", &a, &b, &c);

// 开始分支

pid_t pid = fork ();

if (pid == -1) {

perror ("fork");

return -1;

}

if (pid == 0) {

scanf ("%d%d%d", &a, &b, &c);

printf ("子进程:%d %d %d\n", a, b, c);

return 0;

}

sleep (1);

printf ("父进程:%d %d %d\n", a, b, c);

return 0;

}

输入:

父进程:123456

输出:

子进程:456

父进程:123

4)函数调用后父子进程各自继续运行,

其先后顺序不确定,某些实现可以保证子进程先被调度。

5)函数调用后,父进程的文件描述符表(进程级)也会被复制到子进程中,

二者共享同一个文件表(内核级)。

范例:ftab.c

#include <stdio.h>

#include <string.h>

#include <fcntl.h>

#include <unistd.h>

int main (void) {

int fd = open ("ftab.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);

if (fd == -1) {

perror ("open");

return -1;

}

// 父进程写入一个字符串

const char* text = "Hello, World !"; // 文件指针在 ! 之后那个位置

if (write (fd, text, strlen (text) * sizeof (text[0])) == -1) {

perror ("write");

return -1;

}

// 进入子进程

pid_t pid = fork ();

if (pid == -1) { // 进入子进程失败

perror ("fork");

return -1;

}

if (pid == 0) { // 进入子进程成功

if (lseek (fd, -7, SEEK_CUR) == -1) { // 从当前位置向文件首移动 7 字节,应该在 W 位置

perror ("lseek");

return -1;

}

close (fd); // 只能关闭子进程的文件描述符,而无法关闭文件表,应为父进程还在用;

return 0;

}

sleep (1);

// 应为文件表共享,所以在子进程里面移动了文件位置,对父进程也有影响

text = "Linux";

if (write (fd, text, strlen (text) * sizeof (text[0])) == -1) {

perror ("write");

return -1;

}

// 最后 ftab.txt 里面将显示:Hello Linux

close (fd);

return 0;

}

6)总进程数或实际用户 ID 所拥有的进程数,超过系统限制,该函数将失败。

7)一个进程如果希望创建自己的副本并执行同一份代码,

或希望与另一个程序并发地运行,都可以使用该函数。

8)孤儿进程与僵尸进程。

范例:orphan.c(孤儿进程)

#include <stdio.h>

#include <unistd.h>

int main (void) {

printf ("%u进程:我要调用fork()了...\n", getpid ());

pid_t pid = fork ();

if (pid == -1) {

perror ("fork");

return -1;

}

if (pid == 0) {

sleep (1); // 睡一会,等待父进程死去;

// 验证:如果是孤儿,则 PPID 为 1(init);如果不是,则为上面的 %u 的值;

printf ("\n%u进程:我是被%u进程收养的孤儿进程。", getpid (),

getppid ());

return 0;

}

// 父进程打印完这条语句就死去;

printf ("%u进程:我是%u进程的父进程。\n", getpid (), pid);

return 0;

}

范例:zombie.c(僵尸进程)

#include <stdio.h>

#include <unistd.h>

int main (void) {

printf ("%u进程:我要调用fork()了...\n", getpid ());

pid_t pid = fork ();

if (pid == -1) {

perror ("fork");

return -1;

}

if (pid == 0) {

// 打完下面这段话,子进程就死去;

printf ("%u进程:我是%u进程的子进程,即将成为僵尸...\n",

getpid (), getppid ());

return 0;

}

// 父进程睡一会,等子进程死去;

sleep (1);

printf ("%u进程:我是%u进程的父进程。\n", getpid (), pid);

// 此时查看子进程状态

printf ("执行ps -efl | grep %u,按<回车>退出...", pid);

getchar (); // 创造阻塞条件,只有当父进程结束,init 才回收子进程;

return 0;

}

注意:fork 前的代码只有父进程执行,

fork 后的代码父子进程都有机会执行,受代码逻辑的控制而进入不同分支。

8. vfork

#include <unistd.h>

pid_t vfork (void);

该函数的功能与 fork 基本相同,二者的区别:

1)调用 vfork 创建子进程时并不复制父进程的地址空间,

子进程可以通过 exec 函数族,直接启动另一个进程替换自身,进而提高进程创建的效率。

2)vfork 调用之后,子进程先被调度。

用 fork 调用的话,可能是子进程先被调用,也可能是父进程先被调用;

9. 进程的正常退出

从 main 函数中 return(表示整个程序退出)

int main (...) {

. . .

return x;

}

等价于:

int main (...) {

. . .

exit (x);

}

调用标准 C语言的 exit 函数;

在任何函数里调用 exit(); 都会直接退出整个进程;

#include <stdlib.h>

void exit (int status);

1) 调用进程退出,

其父进程调用 wait/waitpid 函数返回 status 的低 8 位。

2) 进程退出之前,

先调用所有事先通过 atexit/on_exit 回调函数注册的函数,

冲刷并关闭所有仍处于打开状态的标准 I/O 流,删除所有通过 tmpfile 函数创建的文件。

#include <stdlib.h>

int atexit (void (*function) (void));

function - 函数指针,指向进程退出前需要被调用的函数。

该函数既没有返回值也没有参数。

成功返回 0,失败返回非零。

int on_exit (void (*function) (int, void*), void* arg);

function - 函数指针,指向进程退出前需要被调用的函数。

该函数没有返回值但有两个参数:

第一参数来自 exit 函数的 status 参数,

第二个参数来自 on_exit 函数的 arg 参数。

arg - 任意指针,将作为第二个参数被传递给 function 所指向的函数。

成功返回 0,失败返回非零。

3) 用 EXIT_SUCCESS/EXIT_FAILURE 常量宏

(可能是 0/1)作参数,调用 exit() 函数表示成功/失败,提高平台兼容性。

4) 该函数不会返回。

因为调完该函数,进程就结束了,无法返回;

5) 该函数的实现调用了 _exit/_Exit 函数。

10. 调用 _exit/_Exit 函数。

#include <unistd.h>

void _exit (int status);

1) 调用进程退出,

其父进程调用 wait/waitpid 函数返回 status 的低 8 位。

2) 进程退出之前,

先关闭所有仍处于打开状态的文件描述符,

将其所有子进程托付给 init 进程(PID 为 1 的进程)收养,向父进程递送 SIGCHILD 信号。

3) 该函数不会返回。

4) 该函数有一个完全等价的标准 C 版本:

#include <stdlib.h>

void _Exit (int status);

进程的最后一个线程执行了返回语句。

进程的最后一个线程调用 pthread_exit 函数。

5)小结:_exit 是系统调用,_Exit 是标 C 函数;

exit 在底层调用 _exit,在调用 _exit 之前会先调用回调函数 atexit/on_exit;

范例:exit.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

void doexit1 (void) {

printf ("doexit1()...\n");

}

void doexit2 (int status, void* arg) {

printf ("doexit2(%d,%s)...\n", status, arg);

}

int foo (void) {

printf ("foo()...\n");

// exit (EXIT_SUCCESS);

// exit (EXIT_FAILURE);

exit (1234);

// _exit (1234); 不调用 doexit1 和 doexit2

// _Exit (1234); 不调用 doexit1 和 doexit2

return 10;

}

int main (void) {

if (atexit (doexit1) == -1) {

perror ("atexit");

return -1;

}

if (on_exit (doexit2, "再见") == -1) {

perror ("on_exit");

return -1;

}

printf ("foo()函数返回%d。\n", foo ());

// return 1234; main 函数里面的 return 相当于调用 exit();

}

输出结果如下:

foo()...

doexit2(1234, 再见)...

doexit1()...

分析:主函数执行到 printf ("foo()函数返回%d。\n", foo ()); 时,

调用 foo() 函数,foo() 调用 exit;exit 调用 atexit 和 on_exit;

当 atexit/on_exit 执行完后,程序就结束了;

不会执行 printf ("foo()函数返回%d。\n", foo ()); 里的 "foo()函数返回%d。\n" 语句;

11. 进程的异常终止

1)调用 abort 函数,产生 SIGABRT 信号。

2)进程接收到某些信号。

3)最后一个线程对“取消”请求做出响应。

12. 等待子进程终止并获取其终止状态 wait/waitpid(收尸)

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait (int* status); // 等待所有子进程

pid_t waitpid (pid_t pid, int* status, int options); // 等待具体某一个子进程

成功返回终止子进程的 PID,失败返回 -1。

1)当一个进程正常或异常终止时,内核向其父进程发送 SIGCHLD 信号。

父进程可以忽略该信号,或者提供一个针对该信号的信号处理函数,默认为忽略。

2)父进程调用 wait 函数:

若所有子进程都在运行,则阻塞。

若有一个子进程已终止,则返回该子进程的 PID 和终止状态 (通过 status 参数)。

若没有需要等待子进程,则返回失败 -1,errno 为 ECHILD。

3)在任何一个子进程终止前,wait 函数只能阻塞调用进程,而 waitpid 函数可以有更多选择。

4)如果有一个子进程在 wait 函数被调用之前,

已经终止并处于僵尸状态,wait 函数会立即返回,并取得该子进程的终止状态。

5)子进程的终止状态通过输出参数 status 返回给调用者,若不关心终止状态,可将此参数置空。

6)子进程的终止状态可借助 sys/wait.h 中定义的参数宏查看:

WIFEXITED(): 子进程是否正常终止,是则通过 WEXITSTATUS() 宏,

获取子进程调用 exit/_exit/_Exit 函数,所传递参数的低 8 位。

因此传给 exit/_exit/_Exit 函数的参数最好不要超过 255。

WIFSIGNALED(): 子进程是否异常终止,是则通过 WTERMSIG() 宏获取终止子进程的信号。

WIFSTOPPED(): 子进程是否处于暂停,是则通过 WSTOPSIG() 宏获取暂停子进程的信号。

WIFCONTINUED(): 子进程是否在暂停之后继续运行。

范例:wait.c

#include <stdio.h>

#include <sys/wait.h>

int main (void) {

pid_t pid = fork ();

if (pid == -1) {

perror ("fork");

return -1;

}

if (pid == 0) {

int status = 0x12345678;

printf ("子进程:我是%u进程。我要以%#x状态退出。\n", getpid (), status);

// 输出:子进程:我是 20163 进程。我要以 0x12345678 状态退出。

return status;

}

printf ("父进程:我要等待子进程...\n");

int status;

pid = wait (&status); // wait 会一直等到子进程退出;

printf ("父进程:发现%u进程以%#x状态退出了。\n", pid, WEXITSTATUS (status));

// wait 通过 WEXITSTATUS 得到子进程终止状态;

// WEXITSTATUS 取得所传参数 0x12345678 的低 8 位:0x78

// 输出:父进程:发现 20163 进程以 0x78 状态退出了。

return 0;

}

范例:loop.c

#include <stdio.h>

#include <errno.h>

#include <unistd.h>

int main (void) {

int i;

for (i = 0; i < 3; i++) {

pid_t pid = fork ();

if (pid == -1) {

perror ("fork");

return -1;

}

if (pid == 0) {

printf ("子进程:我是%u进程。我要退出了。\n", getpid ());

return 0;

}

}

for (;;) {

printf ("父进程:我要等待子进程...\n");

pid_t pid = wait (0); // 调一次 wait 只能回收一个子进程,所以需要不停循环;

if (pid == -1) { // 返回 -1 有两种情况:子进程都在运行,或者子进程都回收完了;

if (errno != ECHILD) { // 异常退出;

perror ("wait");

return -1;

}

printf ("父进程:已经没有子进程可等了,走喽!\n");

break;

}

printf ("父进程:发现%u进程退出了。\n", pid);

}

return 0;

}

7)如果同时存在多个子进程,又需要等待特定的子进程,可使用 waitpid 函数,其 pid 参数:

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

-1 - 等待任一子进程,此时与 wait 函数等价。

> 0 - 等待由该参数所标识的特定子进程。

0 - 等待其组 ID 等于调用进程组 ID 的任一子进程,

即等待与调用进程同进程组的任一子进程。

<-1 - 等待其组 ID 等于该参数绝对值的任一子进程,

即等待隶属于特定进程组内的任一子进程。

范例:waitpid.c

#include <stdio.h>

#include <unistd.h>

int main (void) {

pid_t cpid[3];

int i;

for (i = 0; i < sizeof (cpid) / sizeof (cpid[0]); i++) {

cpid[i] = fork ();

if (cpid[i] == -1) {

perror ("fork");

return -1;

}

if (cpid[i] == 0) {

printf ("子进程:我是%u进程。我要退出了。\n", getpid ());

return 0;

}

}

for (i = 0; i < sizeof (cpid) / sizeof (cpid[0]); i++) {

printf ("父进程:我要等待%u进程...\n", cpid[i]);

pid_t pid = waitpid (cpid[i], 0, 0);

if (pid == -1) {

perror ("waitpid");

return -1;

}

printf ("父进程:发现%u进程退出了。\n", pid);

}

return 0;

}

8)waitpid 函数的 options 参数可取 0 (忽略)或以下值的位或:

WNOHANG - 非阻塞模式,若没有可用的子进程状态,则返回 0。

WUNTRACED - 若支持作业控制,且子进程处于暂停态,则返回其状态。

WCONTINUED - 若支持作业控制,且子进程暂停后继续,则返回其状态。

范例:nohang.c

#include <stdio.h>

#include <errno.h>

#include <sys/wait.h>

int main (void) {

int i;

for (i = 0; i < 3; i++) {

pid_t pid = fork ();

if (pid == -1) {

perror ("fork");

return -1;

}

if (pid == 0) {

printf ("子进程:我是%u进程。我要退出了。\n", getpid ());

return 0;

}

}

for (;;) {

printf ("父进程:我要等待子进程...\n");

pid_t pid = waitpid (-1, 0, WNOHANG);

// 以非阻塞模式等待所有子进程,且不关心终止状态

if (pid == -1) {

if (errno != ECHILD) {

perror ("waitpid");

return -1;

}

printf ("父进程:已经没有子进程可等了,走喽!\n");

break;

}

if (pid) // waitpid 返回非 0,说明子进程退出了;

printf ("父进程:发现%u进程退出了。\n", pid);

else // waitpid 返回 0,说明子进程没退出;

printf ("父进程:没发现子进程退出,干点儿别的...\n");

}

return 0;

}

13 exec

1)exec 函数会用新进程完全替代调用进程,并开始从 main 函数执行。

2)exec 函数并非创建子进程,新进程取调用进程的 PID。

3)exec 函数所创建的新进程,完全取代调用进程的代码段、数据段和堆栈段。

4)exec 函数若执行成功,则不会返回,否则返回 -1。

5)exec 函数包括六种形式:

#include <unistd.h>

int execl (

const char* path,

const char* arg, ...

);

int execv (

const char* path,

char* const argv[]

);

int execle (

const char* path,

const char* arg,

. . .

char* const envp[]

);

int execve (

const char* path,

char* const argv[],

char* const envp[]

);

int execlp (

const char* file,

const char* arg,

. . .

);

int execvp (

const char* file,

char* const argv[]

);

l: 新程序的命令参数以单独字符串指针的形式传入

(const char* arg, ...),参数表以空指针结束。

v: 新程序的命令参数以字符串指针数组的形式传入

(char* const argv[]),数组以空指针结束。

e: 新程序的环境变量以字符串指针数组的形式传入

(char* const envp[]),数组以空指针结束,

无 e 则从调用进程的 environ 变量中复制。

p: 若第一个参数中不包含“/”,则将其视为文件名,

根据 PATH 环境变量搜索该文件。

范例:argenv.c(打印当前的所有命令行参数和环境变量)

#include <stdio.h>

void printarg (int argc, char* argv[]) {

// 第一个参数是命令行参数个数,第二个参数是命令行参数数组

printf ("---- 命令参数 ----\n");

int i;

for (i = 0; i < argc; i++)

printf ("argv[%d] = %s\n", i, argv[i]);

printf ("------------------\n");

}

void printenv (void) {

printf ("---- 环境变量 ----\n");

extern char** environ;

char** env;

for (env = environ; env && *env; env++)

printf ("%s\n", *env);

printf ("------------------\n");

}

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

printarg (argc, argv);

printenv ();

return 0;

}

输入:

# ./a.out hello word 1234 55.8

输出:

---- 命令参数 ----

argv[0] = ./a.out

argv[1] = hello

argv[2] = word

argv[3] = 1234

argv[4] = 55.8

---- 环境变量 ----

省略 . . .

-----------------

范例:exec.c

#include <stdio.h>

#include <unistd.h>

int main (void) {

char* path = "./argenv"; // 调用上面的 argenv.c

char* file = "argenv";

char* argv[] = {path, "hello", "world", NULL}; // 参数,必须保证最后以 NULL 结尾;

char* envp[] = {"USER=unknown", "PATH=/tmp", NULL}; // 环境,必须保证最后以 NULL 结尾;

/*

if (execl (path, argv[0], argv[1], argv[2], argv[3]) == -1) {

perror ("execl");

return -1;

}

输出:

---- 命令参数 ----

argv[0] = ./argenv

argv[1] = hello

argv[2] = word

---- 环境变量 ----

省略 . . .

-----------------

*//*

if (execv (path, argv) == -1) {

perror ("execv");

return -1;

}

输出结果同上

*//*

if (execle (path, argv[0], argv[1], argv[2], argv[3], envp) == -1) {

perror ("execle");

return -1;

}

输出:

---- 命令参数 ----

argv[0] = ./argenv

argv[1] = hello

argv[2] = word

---- 环境变量 ----

USER=unknown

PATH=/tmp

-----------------

*//*

if (execve (path, argv, envp) == -1) {

perror ("execve");

return -1;

}

输出结果同上

*//*

if (execlp (file, argv[0], argv[1], argv[2], argv[3]) == -1) {

perror ("execlp");

return -1;

}

输出:

---- 命令参数 ----

argv[0] = ./argenv

argv[1] = hello

argv[2] = word

---- 环境变量 ----

省略 . . .

-----------------

(前提:PATH 必须包含当前路径,否则编译器无法找到 argenv.c,会报错;)

*/

if (execvp (file, argv) == -1) {

perror ("execvp");

return -1;

}

/*

输出:

---- 命令参数 ----

argv[0] = ./argenv

argv[1] = hello

argv[2] = word

---- 环境变量 ----

省略 . . .

-----------------

(前提:PATH 必须包含当前路径)

*/

return 0;

}

范例:lsexe.c(实现 ls -l)

思路:在 shell 里键入命令,shell 会创建一个子进程来执行命令,主进程调用控制台;

#include <stdio.h>

#include <unistd.h>

int main (void) {

if (execlp ("ls", "ls", "-l", NULL) == -1) {

perror ("execl");

return -1;

}

return 0;

}

范例:fexe.c(实现 ls -l 和 ps -ef 功能)

#include <stdio.h>

#include <unistd.h>

#include <sys/wait.h>

int main (void) {

// 创建子进程,如果不创建,那么只会执行 ls -l,而不会执行 ps -ef

pid_t pid = vfork ();

if (pid == -1) {

perror ("fork");

return -1;

}

// 实现 ls -l

if (pid == 0) {

if (execlp ("ls", "ls", "-l", NULL) == -1) {

perror ("execl");

return -1;

}

return 0;

}

// 回收子进程

int status;

if (waitpid (pid, &status, 0) == -1) {

perror ("waitpid");

return -1;

}

printf ("ls进程以%#x状态退出。\n", WEXITSTATUS (status)); // %#x 十六进制输出

// 实现 ps -ef

if ((pid = vfork ()) == -1) {

perror ("vfork");

return -1;

}

if (pid == 0) {

char* argv[] = {"ps", "-ef", NULL};

if (execvp ("ps", argv) == -1) {

perror ("execv");

return -1;

}

return 0;

}

if (waitpid (pid, &status, 0) == -1) {

perror ("waitpid");

return -1;

}

printf ("ps进程以%#x状态退出。\n", WEXITSTATUS (status));

return 0;

}

14 system

#include <stdlib.h>

int system (const char* command);

1)标准 C 函数;执行 command,成功返回 command 对应进程的终止状态,失败返回 -1。

2)若 command 取 NULL,返回非零表示 shell 可用,返回 0 表示 shell 不可用。

3)该函数的实现,调用了 fork、exec 和 waitpid 等函数,其返回值:

如果调用 fork 或 waitpid 函数出错,则返回 -1。

如果调用 exec 函数出错,则在子进程中执行 exit(127)。

如果都成功,则返回 command 对应进程的终止状态(由 waitpid 的 status 输出参数获得)。

4)使用 system 函数而不用 fork+exec 的好处是,system 函数针对各种错误和信号都做了必要的处理。

范例:system.c

#include <stdio.h>

#include <unistd.h>

#include <sys/wait.h>

int main (void) {

int status;

if ((status = system (NULL)) == -1) {

perror ("system");

return -1;

}

if (! status) {

printf ("shell不可用!\n");

return -1;

}

if ((status = system ("ls -l")) == -1) {

perror ("system");

return -1;

}

printf ("ls进程以%#x状态退出。\n", WEXITSTATUS (status));

if ((status = system ("ps -ef")) == -1) {

perror ("system");

return -1;

}

printf ("ps进程以%#x状态退出。\n", WEXITSTATUS (status));

return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: