进程控制(二)进程控制编程
获取ID
#include <sys/types.h>
#include <unistd.h>
获取本进程ID
pid_t getpid(void)
获取父进程ID
pid_t getppid(void)
进程创建fork()
#include <unistd.h>
pid_t fork(void)
功能:创建子进程
fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值:
1、在父进程中,fork返回新创建的子进程的PID;
2、在子进程中,fork返回0;
3、如果出现错误,fork返回一个负值
当fork()顺利完成任务时,就会存在两个进程,每个进程都从fork()返回处开始继续执行。
两个进程执行相同的代码(text)段,但是有各自的堆栈(stack)段、数据(data)段以及堆(heap)。
子进程的stack、data、heap segments是从父进程拷贝过来的。
fork()之后,哪一个进程先执行(scheduled to use the CPU)不确定。
pid_t pid = fork();
if (pid == -1)
{
perror ("fork");
return -1;
}
if (pid > 0) // 父进程
{
printf ("我是父进程,pid = %d\n", getpid());
}
else if (pid == 0) // 子进程
{
printf ("我是子进程,pid = %d\n", getpid());
}
int count = 0;
int main()
{
//int count = 0;
pid_t pid = fork();
count++;
printf ("count = %d\n", count);
return 0;
}
结果:
count = 1
count = 1
vfork
vfork 的子进程必须要显示调用 exit(),否则会出现段错误
vfork 子进程和父进程共享数据段
vfork 的子进程先于父进程执行,子进程执行完,CPU才能调度到父进程
int main()
{
int count = 0;
pid_t pid = vfork();
if (pid < 0)
{
perror("vfork");
return -1;
}
count++;
if (pid > 0) // 父进程
{
printf ("我是父进程,pid = %d count = %d\n", getpid(), count);
}
else if (pid == 0) // 子进程
{
printf ("我是子进程,pid = %d count = %d\n", getpid(), count);
while (1);
exit(0);
}
return 0;
}
exec函数族
exec用被执行的程序替换调用它的程序
区别:
fork 创建一个新的进程,产生一个新的PID
exec 启动一个新程序,替换原有的进程,因此进程的PID不会改变。
(1)execl
#include <unistd.h>
int execl(const char * path, const char* arg1,...)
参数:
path : 被执行程序名(含完整路径)。
arg1 - argn: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
// 第一个参数需要一个路径,能够找到需要执行的文件
// 后面的参数表明执行的方式,和在终端方式类似,如果程序执行需要
// 其他的参数 ls -l, 参数要作为函数的实参传过去
// 最后需要补一个 NULL
// 产生一个新的程序 ,替换了原有的程序,原有的进程id是不变的
// int ret = execl("/bin/ls", "ls", "-l", NULL);
int ret = execl("/bin/ps", "ps", "-ef", NULL);
if (ret == -1)
{
perror("execl");
return -1;
}
(2)execlp
#include <unistd.h>
int execlp(const char * path, const char* arg1,...)
参数:
path : 被执行程序名(不含路径时将从path环境变量中查找该程序)。
arg1 - argn: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
int ret = execlp("ls", "ls", "-l", NULL);
if (ret == -1)
{
perror("execl");
return -1;
}
(3)execv
#include <unistd.h>
int execv(const char * path, const char *argv[])
参数:
path : 被执行程序名(含完整路径)。
argv[]: 被执行程序所需的命令行参数数组。
char *a[] = {"file", NULL};
int ret = execv("file", a);
if (ret == -1)
{
perror("execl");
return -1;
}
(4)system
#include <stdlib.h>
int system(const char* string)
功能:
调用fork产生子进程,由子进程来调用 /bin/sh -c string来执行参数string所代表的命令
printf ("hellp world\n");
sleep(1);
// 在内部fork()一个子进程,调用 /bin/sh -c string来执行
system("ps -ef | grep a.out");
可以写一个伪终端
printf ("请选择文件: \n");
system ("ls /home");
char str[100];
while(1)
{
fgets(str, 100, stdin);
printf ("[root@promote 15]# ");
system(str);
}
进程的终止
(1)exit,_exit用于终止进程
区别:
_exit: 直接使进程停止,清除其使用的内存,并清除缓冲区中内容
exit在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。
exit()
#include<stdlib.h>
void exit(int status);
exit()用来正常终结目前进程的执行,并把参数status返回给父进程,而进程所有的缓冲区数据会自动写回并关闭未关闭的文件。
printf ("hello\n");
printf ("111111111111111111111111");
// 在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程
exit(0);
结果:hello
111111111111111111111111
_exit()
#include<unistd.h>
void _exit(int status);
此函数调用后不会返回,并且会传递SIGCHLD信号给父进程,父进程可以由wait函数取得子进程结束状态。
printf ("hello\n");
printf ("111111111111111111111111");
// _exit: 直接使进程停止,清除其使用的内存,并清除缓冲区中内容
_exit(0);
结果:hello
(2)子进程比父进程先退出:僵尸进程
僵尸进程产生的过程:
1. 父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。
2. 子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD 信号,父进程此时应该调用 wait() 系统调用来获取子进程的退出状态以及其它的信息。在 wait 调用之后,僵尸进程就完全从内存中移除。
3. 因此一个僵尸存在于其终止到父进程调用 wait 等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。
int count = 10;
while (count--)
{
pid_t pid = fork();
switch (pid)
{
case -1:
perror ("fork");
break;
case 0: // 子进程
printf ("我是子进程,我的Id 是%d\n", getpid());
printf ("我走啦\n");
exit(0);
default: // 父进程
//printf ("我是父进程,Id = %d\n", getpid());
//while (1);
break;
}
}
while(1);
(3)父进程比子进程先退出:守护进程
若父进程比子进程先终止,则该父进程的所有子进程的父进程都改变为init进程。我们称这些进程由init进程领养。其执行顺序大致如下:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程ID就更改为1(init进程的ID);
有init领养的进程不会称为僵死进程,因为只要init的子进程终止,init就会调用一个wait函数取得其终止状态。这样也就防止了在系统中有很多僵死进程。
让程序运行在后台的方法:
1、./a.out后面加&;
2、创建父子进程,父进程先退出
pid_t pid = fork();
switch (pid)
{
case -1:
perror ("fork");
break;
case 0: // 子进程
printf ("我是子进程,我的Id 是%d\n", getpid());
while (1);
break;
default: // 父进程
printf ("我是父进程,Id = %d\n", getpid());
printf ("我走啦\n");
while (1);
break;
}
守护进程设置成开机启动
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
int daemonize(int nochdir, int noclose)
{
// 1、创建子进程,关闭父进程
pid_t pid = fork();
if (pid > 0)
{
exit(0);
}
else if (pid < 0)
{
return -1;
}
// 2、设置文件的掩码, mode & ~umask
umask(0);
// 3、设置新的会话: 脱离当前会话和终端的控制
if (setsid() < 0)
{
return -1;
}
if (nochdir == 0 )
{
// 4、改变当前的工作目录
if (chdir("/") < 0)
{
return -1;
}
}
// 关闭9标准输入、标准输出、标准错误
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
if (noclose == 0)
{
// 重定向标准输入、关闭标准输出、标准错误
open("/dev/null", O_RDONLY); // 0
open("/dev/null", O_RDWR); // 1
open("/dev/null", O_RDWR); // 2
}
return 0;
}
int main()
{
daemonize(0,0);
//系统自带函数,作用和daemonize(0,0)一样
//daemon(0,0);//系统自带函数
while (1);
return 0;
}
进程等待
wait和waitpid
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
返回值:若成功返回进程ID,若出错返回-1。
调用wait或waitpid的进程可能发生的情况有:
如果所有子进程都还在运行,则阻塞(Block)。
如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
如果它没有任何子进程,则立即出错返回。
在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
waitpid并不等待在其调用之后的第一个终止的子进程。它有若干个选项,可以控制它所等待的进程。
有4个互斥的宏可以用来获取进程终止的原因:
(1) WIFEXITED(status)
若子进程正常终止,该宏返回true。
此时,可以通过WEXITSTATUS(status)获取子进程的退出状态(exit status)。
(2)WIFSIGNALED(status)
若子进程由信号杀死,该宏返回true。
此时,可以通过WTERMSIG(status)获取使子进程终止的信号值。
(3) WIFSTOPPED(status)
若子进程被信号暂停(stopped),该宏返回true。
此时,可以通过WSTOPSIG(status)获取使子进程暂停的信号值。
(4) WIFCONTINUED(status)
若子进程通过SIGCONT恢复,该宏返回true。
wait
#include <sys/wait.h>
pid_t wait(int *status);
如果一个子进程已经终止,并且是一个僵死进程,wait立即返回并取得该子进程的状态,否则wait使其调用者阻塞直到一个子进程终止。如果调用者阻塞并且它有多个子进程,则在其一个子进程终止时,wait就立即返回。因为wait返回终止子进程的ID,所以总能了解到是哪一个子进程终止了。
pid_t pid = fork();
switch (pid)
{
case -1:
perror ("fork");
break;
case 0: // 子进程
printf ("我是子进程,我的Id 是%d\n", getpid());
while(1);
exit(0);
default: // 父进程
printf ("等待子进程挂掉\n");
int status;
pid_t childId = wait(&status);
printf ("成功处理一个子进程, 该子进程是 : %d, %d\n", childId, status);
if ( WIFEXITED(status))
{
printf ("正常死亡\n");
}
else
{
printf ("被人做掉\n");
}
break;
}
waitpid
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid (pid_t pid, int * status, int options)
功能:会暂时停止目前进程的执行,直到有信号来到或子进程结束
参数:如果不在意结束状态值,则参数status可以设成NULL。
参数pid为欲等待的子进程识别码:
pid<-1 等待进程组识别码为pid绝对值的任何子进程。
pid=-1 等待任何子进程,相当于wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为pid的子进程。
参数option可以为0 或下面的OR 组合
WNOHANG: 如果没有任何已经结束的子进程则马上返回,不予以等待。
WUNTRACED :如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
返回值:如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。
pid_t pid = fork();
switch (pid)
{
case -1:
perror ("fork");
break;
case 0: // 子进程
printf ("我是子进程,我的Id 是%d\n", getpid());
while(1);
exit(0);
default: // 父进程
printf ("等待子进程挂掉\n");
int status;
pid_t childId = waitpid(-1, NULL, WNOHANG);
printf ("成功处理一个子进程, 该子进程是 : %d, %d\n", childId, status);
break;
}