进程控制:
1、 进程创建函数: fork();
头文件:
#include<sys/types.h>
#include<unistd.h>
函数原型:
pid_t fork(void);
函数返回值:0:表示此进程现在是子进程;
-1:表示出错;
子进程ID号;(大于零的整数):表示现在此进程时父进程,接收到的ID号是子进程的ID号;
2、fork()返回-1(也就是进程创建出错的原因)
1、系统中拥有了太多的进程;超过了系统的限制;(系统级)
2、该用户有了太多的进程,超过了CHILD_MAX的限制;(用户级);
3、fork()的用途;
1、父进程希望赋值自己的代码,使父子进程不同的代码段;如:网络服务器的经典代码;
2、一个进程要执行一个完全不同的程序;从fork()返回后,立即调用exec()函数,去执行另外的程序;
4、子进程可用通过getpid 获取自己的ID号,也可以通过getppid()获取父进程的ID号;
但是父进程只可以通过getpid获取自己的ID号,不能通过其他函数获取子进程的ID号;唯一获取子进程ID号的机会就是在父进程调用fork()时,获取其返回值,该值就是子进程的ID号;
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* arg1,....,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[]);
注意:除了exec这几个字符外,
函数中含有“l”的表示传递给进程的参数 将由列表形式展出;记住,最后一个参数是NULL,作为哨兵,表示参数列表完毕;传递给进程的第一个参数,相当于main中的argv[0];
函数中含有“v”,表示传递的参数将以指针数组的形式展出;传递给进程的第一个参数,相当于main中的argv[0];
函数中含有“p”,表示不用指定路径,系统将会去环境变量指定的路径中区寻找;不含p的表示一定要指明文件所在的路径;
函数中含有“e”,表示需要用户指定环境变量,不含p的表示该进程继续沿用父进程的环境变量;
只有在出错时,才会有返回值;
出错的几种可能的原因:
第一,路径不正确;
第二,文件不存在;
第三,权限不够;
第四,不知道;
调试要使用perror(“函数名:“)打印调试信息;
6、system(char * string)函数;
作用:执行一条命令“string”;
system()函数调用/bin/sh来执行参数指定的命令,/bin/sh 一般是一个软连接,指向某个具体的shell,比如bash,-c选项是告诉shell从字符串command中读取命令;
在该command执行期间,SIGCHLD是被阻塞的,SIGINT和SIGQUIT是被忽略的,意思是进程收到这两个信号后没有任何动作。
,实际上system()函数执行了三步操作:
1.fork一个子进程;
2.在子进程中调用exec函数去执行command;
3.在父进程中调用wait去等待子进程结束。
对于fork失败,system()函数返回-1。
源码:
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL)
{
return (1); //如果cmdstring为空,返回非零值,一般为1
}
if((pid = fork())<0)
{
status = -1; //fork失败,返回-1
}
else if(pid == 0)
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~
}
else //父进程
{
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTR)
{
status = -1; //如果waitpid被信号中断,则返回-1
break;
}
}
}
return status; //如果waitpid成功,则返回子进程的返回状态
}
7、使用system()函数的建议
system()函数有时很方便,但不可滥用!
1)、建议system()函数只用来执行shell命令,因为一般来讲,system()返回值不是0就说明出错了;
2)、建议监控一下system()函数的执行完毕后的errno值,争取出错时给出更多有用信息;
3)、建议考虑一下system()函数的替代函数popen();
8、进程终止:
进程在终止时,会自动释放在“用户空间”分配的内存,但是PCB还保存着,保存着退出的状态,父进程可以使用wait() 或者waitpid()函数来获取子进程PCB中的信息,然后彻底清除这个子进程;
僵尸进程:父进程还没调用wait() 或者 waitpid()来对已经终止的子进程进行清理工作时,此时子进程的 状态称为僵尸状态;正常情况下,僵尸进程都会被父进程立刻清理掉;
kill 只能终止进程,并不能清理僵尸进程;
如果一个父进程终止,而其子进程未终止,则子进程的父进程会改变为init进程;
init进程时系统中的一个特殊进程,通常位于/sbin/init,进程ID = 1,该进程在启动时负责各种系统服务的启动,之后就负责清理子进程,只要有子进程终止,init就会调用wait()函数清理;
9、进程终止的方式:
正常终止的方式:
1)、void exit(int status);
2)、void _exit(int status);
异常终止的方式:
1)、调用了abort;
2)、由一个信号终止;
10、exit(int status)和_exit(int status)的区别:
exit()先执行一些清除工作,如调用各终止处理程序,把文件缓冲区的内容写回文件,关闭所有标准I/O流,;然后再进入内核;头文件是 stdlib.h;
_exit()则立即进入内核;使用的头文件是 unistd.h;直接是进程停止运行,清除内存空间,销毁内核中的各种数据结构,包括未被写入到文件的缓冲区内容也会被清理掉;
_exit(int status)是底层的系统调用,exit(int status)在底层调用了_exit(int status);
status 用于传递进程结束时的状态;一般来说,0表示正常结束,其他结果都表示非正常结束;使用wait()函数可以接收子进程的返回值,然后根据返回值,进行不同的处理;
11、int atexit(void(* fun)(void));
在调用exit(注意,不是_exit())时,进行清理工作以前,调用各种处理程序,而这些处理函数需要在使用atexit(void(*void)(void))函数进行注册,一个进程最多可以注册32个这样的函数,一旦注册成功,则在系统调用exit()时,会自动执行的这些函数,无需用户干预,值得注意的是,exit()调用的处理函数的执行:顺序与这些函数的注册顺序恰好相反;
12、wait 和waitpid的区别:
pid_t wait(int *status):
如果status 为空,则表示接受任意状态结束的进程,如果status 不为空,则表示接受指定状态结束的进程;
等待任意进程终止,status 用于保存子进程的终止状态;如果处理成功,则返回子进程的PID号;
如果其他子进程都已经结束,或者没有子进程,wait都会立即返回;
pid_t waitpid(pid_t pid, init *status, int options);
pid>0时,表示只等待某个特定的进程结束;
pid=0时,表示等待?????????????????
pid=-1时,表示等待任何一个子进程结束,作用和wait()一样;
pid<-1时,表示?????????????????
status 同wait();
options: 当其值为:WNOHANG时,如果指定的PID的进程还未终止时,不会阻塞等待,而是立即返回,返回值是0;
当其值是:WUNTRACED时,???????????????
13、调用wait 和waitpid时可能出现的情况:
1、阻塞:(如果其他子进程还在运行状态,则会进入阻塞状态进行等待;)
2、带有子进程的终止信息返回(如果一个进程已经终止,正在等待父进程对其终止状态信息进行读取)
3、出错立即返回-1;(如果他没有任何子进程);
14、wait()只是waitpid的一个特例 ;
15、使用wait() 或者 waitpid()可以实现进程之间的同步关系;
16、wait() 和waitpid()参数中的int* status 中包含了多个字段,用宏定义可以提取其中的每个字段;
如果子进程是正常终止的,则WIFEXITED的字段不为0,WEXITSTATUS的值就是其进程退出的状态;
如果子进程是异常终止的,则WIFSIGNALED取值字段不为0,WTERMSIG取出的字段值就是信号编号;
17、守护进程:(Daemon进程)
是linux中的后台服务进程,独立于控制终端,并且周期性的执行某种任务,或者等待处理某些发生的事件;
在系统引导装入时启动,在系统关闭时终止;