进程控制(二)进程控制编程

获取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;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值