linux c 编程实战:进程控制总结(二)

进程操作

1. 创建进程:

  • 系统创建:由它创建的进程是平等的,一般不会存在资源继承的关系,在系统启动时,操作系统会创建一些进程,他们承担着管理和分配系统资源的任务,这些进程也被称为系统进程。
  • 父进程创建:子进程和父进程属于隶属关系,子进程也可以继续创建子进程,这样就会形成一个进程家族。子进程会继承父进程几乎所有的系统资源。接下来讲的都是父进程创建子进程。

2.fork函数:

#include<sys/types.h>
#include<unistd.h>
int fork()
//fork函数非常特殊,它有两个返回值。当fork调用后,当前进程实际上就已经分裂成两个进程,一个是父进程,一个是刚刚创建的子进程,fork函数的两个返回值分别是父进程调用fork函数返回子进程的进程id,一个是子进程调用fork函数返回0,一般会在程序中用这两个返回值来判断当前是哪个进程在执行这个程序。
//进程创建失败会返回-1

补充:

  • fork函数之后到底是子进程先运行,还是父进程先运行这是不确定的,它取决于操作的系统的调度算法,我的ubuntu16.04是保证第一次父进程先运行,然后他们会抢夺cpu,然后这个过程是不确定的,并且抢夺成功的概率两个基本相等。
  • fork函数调用后,操作系统会给每个进程分配相应的时间片,建议百度时间片轮转算法,这样你对fork之后到底哪个进程会有一定的了解,简单来说,cpu是公平的,给两个进程运行的机会也是相近的。
  • fork函数的两次返回值是怎么回事呢,其实,fork函数根本上是只有一个返回值的,有两个返回值是因为它被调用了两次,fork函数会创建一个子进程,子进程是会将父进程的堆栈段复制下来的,然后父进程调用fork返回子进程的id,子进程调用fork返回0,看起来会让我们误认为fork函数有两个返回值一样。
  • sleep函数会让一个进程先暂时挂起,因为cpu内部计算是非常快的,子父进程之间抢占cpu的时间必然是非常短的,假如父进程先运行,然后运行sleep将父进程暂时挂起,这个时候,cpu是不能闲着的,在这个相对计算机来说很长的时间里,此时一定是子进程运行了。所以说,虽然子父进程之间是不能预测哪个先发生的,但有时根据程序的写法,是可以判断出下一次到底是哪个进程开始运行。

附源码:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
    int pid;
    char *str;
    int k;

    pid=fork();

    switch(pid)
    {
        case -1:
            perror("Process creation failed");
        break;

        case 0:
            str="Child process is running";
            k=3;
        break;

        default:
            str="Parent process is running";
            k=5;
        break;
    }

    while(k>0)
    {
        puts(str);

        sleep(1); //sleep会使进程挂起,当一秒被唤醒后,两个进程又争夺cpu

        k--;
    }

    return 0;
}

注意:
子进程会继承父进程的很多属性,包裹用户id,组id,当前工作目录,根目录,打开的文件,创建文时所用的屏蔽字等等,但是,子进程和父进程也是有不同属性的,比如下面这些:

  • 子进程有自己的id
  • fork的返回值不一样
  • 不同的父进程id
  • 子进程共享父进程的文件描述符,但父进程对文件描述符的改变并不会影响到子进程中的文件描述符
  • 子进程不会继承父进程设置的文件锁
  • 子进程不继承父进程设置的警告
  • 子进程的未决信号集被清空

3.孤儿进程

当创建了一个子进程后,如果父进程比子进程先行结束,子进程就会变为一个孤儿进程,它会由init进程收养,在ubuntu下有所改变,是被一个upstart的进程收养(在图形化界面下收养孤儿进程,在文本界面没有什么影响)

4.vfork函数

vfork函数的作用和fork函数基本一样,但他们有几点很不一样的地方:

  • vfork函数创建子进程会保证子进程先运行
  • vfork不会像fork函数那样将父进程的资源复制下来重新分配地址空间供子进程使用,它会和父进程共享内存空间,也就是说,父进程或子进程对进程的影响都会同时影响到对方,因为是共用一段内存空间的。
  • 因为vfork是保证子进程先运行的,然后只有在调用exit或exec函数族,父进程才会被调度,所以如果子进程在运行过程中要依赖父进程的某个行为的话,就会导致死锁。
  • 注:因为fork函数创建子进程会将父进程的资源几乎全部复制下来,所以这是非常浪费系统资源的,而vfork是和父进程共享一段内存,所以,这就会大大减小系统的开销。
  • 写时复制:由于fork函数是将父进程的资源直接拷贝一份给子进程的,并且给子进程分配足够的物理空间,这样做过于简单并且效率低下,unix(like)系统是非常讲究效率的,所以写时复制技术就是解决这种问题,写时复制在调用了fork之后并不会立即复制父进程的资源给子进程,而是先让子进程和父进程共享整个进程的物理空间,举例来说,fork()后立即调用exec()—它们就无需复制了。fork()的实际开销就是复制父进程的页表以及给子进程创建惟一的进程描述符,只有当子父进程相应段有所改变的时候,才会将父进程的一份资源拷贝到子进程下(更详细的内容在以后我会讲)。

5.创建守护进程

守护进程是指在后台进行的,没有控制终端与之相连的进程,独立于控制终端,周期性的执行某种任务。守护进程在后台运行,类似于windows下的系统服务。
编写守护进程的步骤:

  • 调用fork产生一个子进程,然后使父进程退出
  • 使用setsid创建一个新的会话期。控制终端,登录会话,进程组通常是从父进程继承下来的,守护进程是要摆脱他们的,方法就是调用setsid使进程成为一个会话组长。(会话过程对控制终端的独占性会使新的会话组长脱离原来的控制终端)
  • 禁止重新打开终端。由于无控制终端的会话组长和进程组长会自动申请打开一个新的终端,这时就需要在fork一次创建新的子进程使它不在是会话组长或进程组长
  • 关闭不再需要的文件描述符,避免系统资源浪费
  • 将当前目录更改为/目录,守护进程工在哪个目录,哪个目录不能被卸载
  • 将文件屏蔽字设置为0(文件的权限完全由用户指定,系统不干预),进程从创建它的父进程那里继承的文件创建屏蔽字可能会拒绝某些许可权
  • 处理SIGCHLD信号,信号判断请大家自行百度,这一步并不是必须的,但对于某些进程是很有必要的。

附源码:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<time.h>
#include<signal.h>
#include<sys/param.h>
#include<sys/stat.h>
#include<syslog.h>

int init_daemon()
{
    int pid;
    int i;

    //忽略终端I/O信号,STOP信号
    signal(SIGTTOU,SIG_IGN); // 忽略后台程序写终端
    signal(SIGTTIN,SIG_IGN); // 忽略后台程序读终端
    signal(SIGTSTP,SIG_IGN); // 忽略CTRL+Z,挂起
    signal(SIGHUP,SIG_IGN);


    //创建一个子进程并且关闭父进程
    pid = fork();

    if(pid > 0)
    {
        exit(0);
    }
    else if(pid == -1)
    {
        return -1;
    }

    //设置子进程为进程组长
    setsid();

    //在创建一个子子进程,使子进程不能唤醒新的终端
    pid = fork();
    if(pid > 0)
    {
        exit(0);
    }
    else if(pid == -1)
    {
        return -1;
    }

    //关闭子进程继承的所有的文件描述符
    for(i=0;i<NOFILE;close(i++))

    //改变子子进程的工作目录
    chdir("/");

    //将文件屏蔽字设为0
    umask(0);

    //忽略SIGCHLD信号
    signal(SIGCHLD,SIG_IGN); // 进程终止或停止时,SIGCHLD会发送给它的父进程
}

int main()
{
    time_t now;
    init_daemon();
    syslog(LOG_USER|LOG_INFO,"测试守护进程!\n");
    while(1)
    {
        sleep(8);
        time(&now);
        syslog(LOG_USER|LOG_INFO,"系统时间:\t%s\t\t\n",ctime(&now));
    }

    return 0;
}

你可能感兴趣的:(编程,linux,函数,操作系统)