进程函数接口

1.创建进程

  • fork函数创建进程
#include 

/*
功能:创建子进程
参数:无
返回值:失败返回-1
*/
pid_t fork(void);

注意:
1.创建成功时候,父进程返回子进程的进程号,子进程返回0
2.通过fork返回值区分父进程和子进程
举例:

#include 
#include 

int main(void)
{
    int count = 0;
    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork");
        return -1;
    }
    else if (pid == 0) {
        printf("child process : my pid is %d\n", getpid());
        count++;
        printf("%d\n", count);
    }
    else {
        printf("parent process : my pid is %d\n", getpid());
        count++;
        printf("%d\n", count);
    }
    return 0;
}

结果如下:

hanqi@hanqi-PC:~/C/process$ ./a.out 
parent process : my pid is 8885
1
child process : my pid is 8886
1

1.此处在pid=fork()之前,只有1个进程在执行这段代码,但在这条语句之后,就变成2个进程在执行了,这两个进程的几乎完全相同.此处比较神奇的是,从执行结果来看fork()执行了1次却返回了2个值,一个等于0,一个大于0. 其中等于0的是子进程,大于0的是父进程.
2.创建新进程成功后,在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID.此时系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
3.执行完fork后,进程1的变量为count=0,进程2的变量为count=0,这两个进程的变量都是独立的存在不同的地址中,不是共用的,这点要注意.可以说,我们就是通过fpid来识别和操作父子进程的。

  • vfork创建进程
    1.vfork直接使用父进程的存储空间,不拷贝
    2.vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行

例如:

#include 
#include 
#include 
#include 

int main(void)
{
    pid_t pid;
    pid = vfork();
    if(pid > 0)
    {
        while(1)
        {
            printf("father process: %d\n", getpid());
            sleep(1);
        }
    }
    else if(pid == 0)
    {
        while(1)
        {
            printf("son process: %d\n", getpid());
            sleep(1);
            //exit(0);
        }
    }
    return 0;
}

子进程没有exit(0)的时候,程序会一直打印子进程,不会执行父进程代码.子进程有exit(0)的时候先执行子进程代码,再执行父进程代码

2.父子进程关系

  • 子进程继承了父进程内容,包含父进程代码,变量,堆栈,文件等
  • 父子进程有独立的地址空间,互不影响
  • 若父进程先结束,则其子进程成为孤儿进程,被init进程(pid=1)收养;且子进程变成后台进程
  • 若子进程先结束,如果父进程没有及时回收,子进程会变成僵尸进程

3.其他思考

  • 子进程从何处开始运行?
    答:子进程从fork()的下一条语句开始执行.因为在父进程中执行fork()时候,其PC指针寄存器指向fork()下一条语句,此时子进程被创建.子进程会复制父进程内容,包括PC指针寄存器,所以此时子进程PC寄存器与父进程PC寄存器相同,故执行fork()下一条指令.
  • 父子进程执行顺序?
    答:按照内核调度执行 ,谁都可以先执行
  • 父进程能否多次调用fork?子进程呢?
    答:父进程可以多次调用fork,每调用一次fork就会创建一个新的子进程.子进程亦可,调用后产生孙进程

4.结束进程

  • exit/_exit函数结束进程
/*
功能:结束进程
参数:返回给父进程的状态值
返回值:无
*/
#include 
#include 

void exit(int status);
void _exit(int status);

注意:
1.exit头文件是stdlib.h,_exit是unistd.h
2.exit结束进程时候会刷新缓冲区,_exit()不会

举例:

#include 
#include 

int main(void)
{
    printf("hello");
    exit(0);
    printf("world");
}

结果如下:

hanqi@hanqi-PC:~/C/process$ ./a.out
hellohanqi@hanqi-PC:~/C/process$ gcc test.c 

分析:打印hello时候没有加入换行符,所以数据不会立刻打印到终端上,而是存放在缓冲区内,当执行exit时候,会刷新缓冲区,打印hello,之后退出进程,故没打印world.

#include 
#include 

int main(void)
{
    printf("hello\n");
    printf("world");
    exit(0);
}

结果如下:

hanqi@hanqi-PC:~/C/process$ ./a.out
hello
worldhanqi@hanqi-PC:~/C/process$ 
#include 
#include 

int main(void)
{
    printf("hello\n");
    printf("world");
    _exit(0);
}

执行结果如下:

hanqi@hanqi-PC:~/C/process$ ./a.out
hello
hanqi@hanqi-PC:~/C/process$ 

分析:可以看到,并未打印world,因为word被存入缓冲区,而_exit不会刷新缓冲区,所以不会打印

5.exec函数族

  • 进程调用exec()函数族执行某个程序
  • 此时进程当前内容被指定的程序替换,子进程PID不变
  • 最终实现让父子进程执行不同的程序
  • 优点:可调用系统指令,使得代码更简洁
  • 实现步骤:
    1.父进程创建子进程
    2.子进程调用exec函数族
    3.父进程不受影响
    如shell程序
  • execl/execlp函数
#include 

/*
功能:在子进程中执行指定程序
参数:
参数1:指定程序路径及名称
参数2:传递给执行程序的参数列表
返回值:失败返回-1,成功不返回
*/
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);

注意:
1.execl函数,且最后一个参数为空指针NULL
2.execlp函数第一个参数直接传入程序名称,函数自动在PATH中查找路径

举例:执行ls命令,显示/etc目录下所有文件的详细信息

if (execl("/bin/ls", "ls", "-a", "-l", "/etc", NULL) < 0) {
    perror("execl");
}
if (execlp("ls", "ls", "-a", "-l", "/etc", NULL) < 0) {
    perror("execl");
}
  • execv/execvp函数
#include 

/*
功能:在子进程中执行指定程序
参数:
参数1:指定程序路径及名称
参数2:传递给执行程序的参数数组
返回值:失败返回-1,成功不返回
*/
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

举例:执行ls命令,显示/etc目录下所有文件的详细信息

char *arg[] = {"ls", "-a", "-l", "/etc", NULL};
if (execv("/bin/ls", arg) < 0) {
    perror("execl");
}
if (execlvp("ls", arg) < 0) {
    perror("execl");
}

6.system

  • 创建子进程并执行command命令,即shell指令
  • 其内部就是调用了exec函数族
  • 子进程结束后会回到父进程执行
#include 

/*
功能:创建子进程并执行command命令
参数:执行命令
返回值:失败返回-1,成功返回命令command的返回值
*/
int system(const char *command);

注意:
父进程会等待子进程command执行结束后才继续执行

6.5popen

  • ponen功能与system类似,都是执行特定指令
  • 但是popen会返回运行结果
#include 

/*
功能:创建子进程并执行command命令,返回执行结果
参数:
参数1:要执行的命令
参数2:类型,读(r)或写(w)
返回值:失败返回-1,成功返回一个流指针,并记录结果指令执行结果的字节数
*/
FILE *popen(const char *command, const char *type);
  • 举例:
#include 
#include 
#include 

int main()
{
    char res[1024] = {0};
    FILE *fp;
    fp = popen("ps", "r");
    int read_byte = fread(res, sizeof(char), 1024, fp);
    printf("read %d byte, res is \n%s", read_byte, res);
    return 0;
}

此处popen函数会调用ps指令查看当前进程信息,并将查询结果给res数组保存,组后打印

  • 结果如下:
read 146 byte, res is 
   PID TTY          TIME CMD
  4783 pts/1    00:00:00 bash
  7117 pts/1    00:00:00 a.out
  7118 pts/1    00:00:00 sh
  7119 pts/1    00:00:00 ps

7.进程回收

  • 在linux系统下子进程结束时候由父进程回收
  • 孤儿进程统一由init进程回收
  • 子进程先结束,如果父进程没有及时回收,子进程会变成僵尸进程
  • 函数wait
#include 

/*
功能:回收子进程
参数:指向保存子进程返回值和结束方式的地址或者NULL,表示父进程不接受返回值
返回值:失败返回-1,成功返回被回收的子进程的进程号
*/
pid_t wait(int *status);

注意:
1.wait函数由父进程调用
2.若子进程没有结束,则父进程会一直处于阻塞状态
3.若父进程有多个子进程,哪个子进程先结束就先回收哪个子进程

进程回收举例:

int status;
pid_t pid;
if ((pid = fork()) < 0) {
    perror("fork");
    exit(-1);
}
else if (pid == 0) {
    sleep(1);
    exit(2);//子进程执行
}
else {
    wait(&status);
    printf("%x\n", status;)
}
  • 子进程正常结束方式
    1.子进程通过exit/_exit/return返回某个值(0~255),即返回数据的低8位,如果超过8位,则高位会被舍弃
    2.父进程调用wait(&status)回收
WIFEXITED(status)  //判断子进程是否正常结束,非正常返回0
WEXITSTATUS(status)  //获取子进程返回值
WIFSIGNALED(status)  //判断子进程是否被信号结束
WTERMSIG(status)  //获取结束子进程的信号类型

status为int类型,一共32位,但只有低16为有效
bit0~bit6代表子进程结束方式,如果为0表示子进程正常结束,非0表示子进程被信号结束且值为结束信号类型
bit8~bit15代表子进程正常结束时候的返回值

  • 举例:
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    pid_t pid;
    int status = 10;
    pid = fork();
    if(pid > 0)
    {
        wait(&status);
        printf("child process exit,status=%d\n", WEXITSTATUS(status));
        while(1)
        {
            printf("father process: %d\n", getpid());
            sleep(1);
        }
    }
    else if(pid == 0)
    {
        while(1)
        {
            printf("son process: %d\n", getpid());
            sleep(1);
            exit(3);
        }
    }
    return 0;
}

使用WEXITSTATUS宏来获取子进程返回值,此处为3,而且父进程阻塞在wait函数处,直到子进程结束

  • 函数waitpid
#include 

/*
功能:回收子进程
参数:
参数1:指定回收的对象,即子进程进程号
参数2:指向保存子进程返回值和结束方式的地址或者NULL,表示父进程不接受返回值
参数3:指定回收方式参数为:0(阻塞方式)或者WNOHANG(非阻塞)
返回值:失败返回-1,成功返回被回收的子进程的进程号或者0(此时表示子进程未结束)
*/
pid_t waitpid(pid_t pid, int *status, int option);
  • 举例:
waitpid(pid, &status, 0);//以阻塞方式回收指定子进程
waitpid(pid, &status, WNOHANG);//以非阻塞方式回收指定子进程
waitpid(-1, &status, 0);//以阻塞方式回收任意子进程
waitpid(-1, &status, WNOHANG);//以非阻塞方式回收任意子进程
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    pid_t pid;
    int status = 10;
    pid = fork();
    if(pid > 0)
    {
        waitpid(pid, &status, WNOHANG);
        printf("child process exit,status=%d\n", status);
        while(1)
        {
            printf("father process: %d\n", getpid());
            sleep(1);
        }
    }
    else if(pid == 0)
    {
        int cnt = 0;
        while(1)
        {
            printf("son process: %d\n", getpid());
            sleep(1);
            cnt++;
            if(cnt >= 4)
            {
                exit(3);
            }
        }
    }
    return 0;
}

注意:使用非阻塞方法仍然会产生僵尸进程

9.获取进程号

  • 函数getpid
#include 
#include 

/*
功能:获取进程号
参数:无
返回值:当前程序进程号
*/
pid_t getpid(void);
  • 举例,获取当前程序进程号
#include 
#include 
#include 

int main()
{
    pid_t pid;
    pid = getpid();
    printf("pid = %d\n", pid);
    while(1);
    return 0;
}

8.守护进程

  • 守护进程概念:
    1.守护进程(Daemon)是Linux三种进程类型之一
    2.守护进程通常在系统启动时运行,系统关闭时结束
    3.守护进程在Linux系统中大量使用,很多服务程序以守护进程形式运行
  • 守护进程特点:
    1.始终在后台运行
    2.独立于任何终端
    3.周期性的执行某种任务或等待处理特定事件
  • 会话,控制终端概念
    1.Linux通过会话(session),进程组的方式管理进程
    2.每个进程属于一个进程组,即每运行一个程序就创建了一个进程组
    3.会话是一个或多个进程组的集合.通常用户打开一个终端时,系统会创建一个会话.所有通过该终端运行的进程都属于这个会话,其中首进程为shell进程
    4.终端关闭时,所有相关的进程都会被结束
  • 守护进程创建步骤:
    1.创建子进程,父进程退出,此时子进程会变为孤儿进程,被init进程收养,子进程会在后台运行,但此时子进程仍然依附于终端
if(fork() > 0){
  exit(0);
}

2.子进程创建新会话,此时子进程会成为新的会话组组长,子进程脱离原终端会话

if(setsid() > 0){
  exit(0);
}

3.更改当前工作目录,守护进程一直在后台运行,其工作目录不能被卸载

chdir("/");
chdir("/temp"); //权限为0777

4.重新设定文件权限掩码为0,掩码只对当前进程有效

if(umask(0) < 0){
  exit(-1);
}

5.关闭打开的文件描述符,关闭从父进程继承的文件,即关闭父进程打开的文件.因为守护进程独立于终端,而其本身继承了父进程的标准输入输出错误流,这些文件依赖于原来父进程的会话,所以在子进程中无法使用,需要关闭

int i;
for(i = 0; i < getdtablesize(); i++)
{
  close(i);
}

举例:创建守护进程,每个1s将系统时间写入文件time.log

#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char* argv[])
{
    pid_t pid;
    FILE* fout;
    time_t t;
    //创建子进程
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(-1);
    }
    //关闭父进程,让子进程变为孤儿进程
    else if (pid > 0) {
        exit(0);
    }
    setsid();//子进程创建新会话
    umask(0);//修改文件权限掩码
    chdir("/tmp");//修改工作目录
    for (int i = 0; i < getdtablesize(); i++) {
        close(i);//关闭父进程文件描述符
    }
    //创建输出流
    if ((fout = fopen("time.log", "a")) == NULL) {
        perror("fopen");
        exit(-1);
    }
    while (1)
    {
        time(&t);
        fprintf(fout, "%s", ctime(&t));
        fflush(fout);
        sleep(1);
    }
}

执行结果:在/tmp文件夹下每过1秒会在后台打印系统时间给time.log

你可能感兴趣的:(进程函数接口)