1.1进程和程序的区别
1.2查看一下进程
1)简单形式:ps命令 默认打开终端
2)ps的参数:
ps -aux 查看计算机中所有的进程(linux专用)
-a:所有用户有控制终端的进程
-u:以相近方式列出
-x:显示无控制终端的进程(守护进程)
ps -elf (linux/unix)
-e:显示所有用户的进程
-l:按照常格式显示
-f:完整格式显示
1.3为什么要引入多进程?
随着CPU运算能力的提高,为了提高CPU的使用效率,引入了多进程机制,使得终端用户可以在一台机械同时并行做N件事。宏观上并行,微观上串行。
1.4关于父进程和子进程
操作系统中的多进程是有启动次序的。UNIX先启动的进程是0号进程。0号进程启动1号进程,再由1号进程,其它进程再启动其它进程。
如果进程A启动了进程B,那么A叫做B的父进程,B叫做A的子进程。
进程用进程号PID来表示。
进程用进程PID来表示,PID是一个非负整数,PID可以延时重用。同一时刻保持唯一性
如果获取一个进程的PID:
getpid():获取进程的PID
getppid():获取父进程的PID
1.5 创建一个子进程的方法
1)fork()创建子进程
2)vfork()| execl() 创建子进程
1.5.1 fork()
fork()是一个非常简单又复杂的函数pid_t fork(void);
返回子进程的PID,或者0
失败返回-1
fork()通过复制父进程的内存空间,创建子进程,复制除了代码区域之外的所有区域,共享代码区域父子进程共享。
1)fork()会创建子进程,子进程从fork()当前位置开始执行代码,fork()之前的代码父进程执行一次,fork()之后的代码父子进程个执行一次。2)fork()会返回两次,父进程返回子进程的PID,子进程返回0
2 多进程编程
为什么引进多进程?
ps -elf
ps -aux
系统首先启动0号进程,然后由0号启动1号,然后由1号进程启动其它进程,其它进程再启动其它进程
父子进程的概念
每个进程都有一个PID,PID是一个非负整数
3 如何让创建子进程
1)fork()
2)vfork()+ excel()
3.1 fork
pid_t fork(void)
复制父进程的内存空间,创建子进程,复制除了代码区域之外的所有区域,共享代码区域父子进程共享。
fork()返回两次,在父进程中执行fork()的return返回子进程的PID,在子进程中执行的fork返回0。
fork() 之前的代码只有父进程执行,fork()之后的代码父子进程都有机会执行,受代码逻辑的控制而进入不同的分支。
问题1:forktest.c对应的a.out执行会开启多少个进程?
fork()创建子进程时会复制代码区之外的所有区域,包括缓冲区。子进程中创建的变量父进程不能使用。
fork()之后先执行子进程还是父进程,不确定。
fork()创建子进程时,如果父进程存在文件描述符,则子进程复杂文件描述符,不复制文件表,共用同一个文件表(offset)。
如果父进程先于子进程结束,子进程编程孤儿进程,该孤儿进程被1号进程1号进程收养。
如果子进程结束时,会给父进程发信号,如果父进程没有及时处理,子进程将变成僵尸(zombie)进程,直达父进程处理了该信号或父进程退出。
4.如何退出进程?
int main()
{
...
return 0;
return -1;
//交给他的父进程
}
4.1 进程的退出方式
1)正常退出
a.在main函数中执行return语句
b.exit函数退出进程
c._exit和_Exit函数
d.最后一个线程退出
f.主线程退出
2)非正常退出
a.被信号打断导致退出,例如:ctrl+c
b.进程中的最后一个线程被取消
4.2 exit()、 _exit()和_Exit()
1)_exit()和_Exit()作用是一样的,区别在于头文件不同,一个是UC函数,一个标c的函数
_exit():#include <unistd.h>
_Exit():#include <stdlib.h>
2)_exit()和_Exit()立即使进程退出
3)exit不是立即退出,可以调试执行一些其他函数后再退出。
可以使用atexit()函数注册一系列函数,然后这个函数在exit退出进程之前被调自动调用。
当执行exit函数或者在主函数里面执行return函数时,都会调用这些函数。
exit(n)中的n值不能超过255,超过也无意义。
4.3 取得子进程的退出状态和退出码
wait()函数和waitpid()函数可以让父进程等到子进程结束,并取得子进程的退出状态和退出
码(return语句后面的值或者exit(n))。
4.3.1 wait()
等待任意一个子进程结束后就返回。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
status是一个传出参数,用来带回结束子进程的退出状态和退出码
返回值是结束的子进程的PID,否则等待该父进程对应的子进程结束(阻塞等待)
出错返回-1,errno ECHILD
宏:WIFEXIT(status)
宏:WIFWXITED(status) 判断进程是否正常结束(exit return _exit _Exit)
WEXITSTATUS(status)
等待子进程结束
4.3.2 waitpid()
用来选择等待某个特定的子进程结束,再返回
pid_t waitpid(pid_t pid, int *status, int options);
5 vfork + exec
#include <sys/types.h>
#include <unistd.h>
vfork和fork在语法上没有任何区别,唯一的区别在于vfork不复制父进程的任何资源,而是直接占用父进程的资源运行代码,父进程处于阻塞状态,直到子进程结束或者调用了exec系列函数
excl是一系列的函数,6个函数,使用全新的的程序替换当前进程中的代码段,数据段、堆区、栈区、保留原进程的PID:
execl/execlp/execle
execv/execvp/execve
前四个字符统一为:exec
第五个字符的取值为:l list:参数传递为逐个例举的方式
v vector:参数传递为构造指针数组的方式
第六个字符的取值为:e environ:可以给新进程传递环境变量表
p path:可以传递路径
execl:第一参数执行文件的路径+名称
第二参数为通常执行文件的名称
后续参数,该文件执行需要的具体参数,最后一个参数必须为NULL
execl("/bin/ls", "ls", "-l", "/home/administrator", NULL);
execlp:第一个参数执行文件的名称,不用加路径,会自动从path指定的路径中寻找要执行的程序。
后续参数与execl相同
execlp("ls", "-l", "/home/administrator", NULL);
execvp:最后一个参数必须为NULL
char arg[]={"/bin/ls", "ls", "-l", "/home/administrator", NULL}
execvp("ls", arg);
execv:
char arg[]={"ls", "-l", "/home/administrator", NULL}
execvp("ls", arg);
vfork + exec 的使用:
用途:1)可以直接编程调用系统中提供的可执行命令
2)降低了模块之间的耦合度
缺点:以上用法去调用系统命令时会有一些列的问题,例如通配符,重定向问题
execl("bash", "bash", "-c", "ls *.c>a.txt", NULL);
system(...):
用shell来调用程序 = fork + exec + waitpid
该函数的参考实现《unix高级编程》p167
system("ls *.c>a.txt")
system 和 vfork + exec 哪个效率更高?
vfork + exec 的效率更高。