【Linux操作系统】进程详解(下)

本文通过示例代码详细介绍了 Linux 中进程相关函数的使用方法,包括解决父子进程共用光标问题、守护进程的创建流程及其实现。并通过具体实例展示了 getpid/getppid、exit/_exit、wait/waitpid 函数的应用。
摘要由CSDN通过智能技术生成


前言

前文【Linux操作系统】进程详解(上)详细讲解了进程相关概念的知识。
本文详细介绍进程相关函数和守护进程的相关内容。


一、父子进程共用光标问题

1.1 验证

代码如下(示例):

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc,const char * argv[])
{
    pid_t pid;
    int fd;
    char ch;
    if((fd=open("./hello.txt",O_RDONLY))==-1)
    {
        PRINT_ERR("open error");
    }
    pid = fork();
    if(pid == -1){
        PRINT_ERR("fork error");
    }else if(pid == 0){
        lseek(fd,5,SEEK_SET);
        read(fd,&ch,1);
        printf("这是子进程%c\n",ch);
    }else{
        sleep(5);
        read(fd,&ch,1);
        printf("这是父进程%c\n",ch);
    }
    close(fd);
    return 0;
}

结果展示:
在这里插入图片描述
总结:
将子进程的光标向后移动5位后输出的是6,父进程因为和子进程共用的一个光标所以父进程在读取的时候并不是1,而是输出了7。
所以为了避免父子进程共用光标的问题,我们不应该在创建进程函数fork前打开文件,应该在各自的进程内打开或者可以打开文件两次,父子进程使用不同的文件描述符。

1.2 规避共用光标问题使用多进程拷贝同一个文件

代码如下(示例):

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int file_init_len(const char *src,const char *dest)
{
    int fd,fd1;
    int len;
    if((fd=open(src,O_RDONLY))==-1)
    {
        PRINT_ERR("open src error");
    }
    if((fd1=open(dest,O_RDWR|O_CREAT|O_TRUNC,0666))==-1)
    {
        PRINT_ERR("open dest error");
    }
    len=lseek(fd,0,SEEK_END);
    close(fd);
    close(fd1);
    return len;
}
int file_copy(const char *src,const char *dest,int start,int len)
{
    int fd,fd1;
    int count =0;
    int ret;
    char buf[128]={0};
    if((fd=open(src,O_RDONLY))==-1)
    {
        PRINT_ERR("open src error");
    }
    if((fd1=open(dest,O_WRONLY))==-1)
    {
        PRINT_ERR("open dest error");
    }
    lseek(fd, start, SEEK_SET);
    lseek(fd1, start, SEEK_SET);
    while((ret=read(fd,buf,sizeof(buf)))!=0)
    {
        count+=ret;
        if(count>=len)
        {
            write(fd1,buf,ret-(count-len));
            break;
        }
        write(fd1,buf,ret);
    }
    close(fd);
    close(fd1);
    return 0;
}
int main(int argc,const char * argv[])
{
    pid_t pid;
    int len;
    //1.判断传参是否正确./a.out srcfile destfile
    if(argc!=3)
    {
        fprintf(stderr, "input error,try again\n");
        fprintf(stderr, "usage:./a.out srcfile destfile\n");
        return -1;
    }
    //2.求源文件有多少字节
    len=file_init_len(argv[1],argv[2]);
    //3.父子进程拷贝
    pid = fork();
    if(pid == -1){
        PRINT_ERR("fork error");
    }else if(pid == 0){
        file_copy(argv[1],argv[2],0,len/2);
    }else{
        file_copy(argv[1],argv[2],len/2,len-len/2);
    }
    return 0;
}

结果展示:
在这里插入图片描述
总结:
父子进程调用拷贝函数都打开了文件,所以并不是共用一个光标。

二、进程相关函数

2.1 getpid/getppid函数

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

pid_t getpid(void);
功能:获取当前进程的进程号
参数:无
返回值:总是会成功了,返回进程的pid

pid_t getppid(void);
功能:获取父进程的进程号
参数:无
返回值:总是会成功了,返回进程的ppid

代码如下(示例):

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc,const char * argv[])
{
    pid_t pid;
    pid = fork();
    if(pid == -1){
        PRINT_ERR("fork error");
    }else if(pid == 0){
        printf("这是子进程");
        printf("pid=%d,ppid=%d\n",getpid(),getppid());
    }else{
        sleep(1);
        printf("这是父进程");
        printf("pid=%d,ppid=%d,cpid=%d\n",getpid(),getppid(),pid);
    }
    return 0;
}

结果展示:
在这里插入图片描述
总结:
getpid用来获取当前进程的进程号,getppid用来获取当前进程的父进程号。

2.2 exit/_exit函数

return本身并不是用来结束进程的,return只有在main函数中,退出main函数的栈的时候整个进程会结束,但是如果return放在其他函数中,并不会结束进程。

#include <unistd.h>
void _exit(int status);
功能:用来结束一个进程,它是系统调用,退出进程的时候不会刷新缓冲区
参数:
    @status:进程退出的时候的状态值 (一般 0成功,1失败)
返回值:无

#include <stdlib.h>
void exit(int status);
功能:用来结束一个进程,它是库函数,退出进程的时候会刷新缓冲区
参数:
    @status:进程退出的时候的状态值 
    (一般 EXIT_SUCCESS 0成功,EXIT_FAILURE 1失败)
返回值:无

exit代码如下(示例):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)
    
int main(int argc,const char * argv[])
{
    pid_t pid;
    pid = fork();
    if(pid == -1){
        PRINT_ERR("fork error");
    }else if(pid == 0){
        printf("这是子进程\n");//打印
        printf("111111");//打印
        exit(EXIT_SUCCESS);
        printf("222222\n");//不打印
    }else{
        printf("这是父进程\n");
        while(1);
    }
    return 0;
}

结果展示:
在这里插入图片描述
_exit代码如下(示例):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)
    
int main(int argc,const char * argv[])
{
    pid_t pid;
    pid = fork();
    if(pid == -1){
        PRINT_ERR("fork error");
    }else if(pid == 0){
        printf("这是子进程\n");//打印
        printf("111111");//不打印
        _exit(EXIT_SUCCESS);
        printf("222222\n");//不打印
    }else{
        printf("这是父进程\n");
        while(1);
    }
    return 0;
}

结果展示:
在这里插入图片描述
总结:
从以上两个代码进行对比可知
exit在结束进程的时候会进行刷新缓冲区,而_exit则不会刷新缓冲区。

2.3 wait/waitpid函数

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);
功能:阻塞等待子进程结束,子进程结束的时候为子进程回收资源,并能够获取到子进程退出时候的状态
参数:
    @wstatus:接收子进程退出的状态 
        (其中8-15这8个bit位是子进程退出的状态)
        (其中0-6这7个bit为是信号号)
返回值:成功返回后回收掉资源的子进程的pid
       失败返回-1,置位错误码
        
 WIFEXITED(wstatus)   //如果子进程是通过exit/_exit/return main 这是正常结束程序的话,它返回真
                      //如果是信号杀死的进程,这个函数返回假
 WEXITSTATUS(wstatus) //获取进程退出的状态
 WIFSIGNALED(wstatus) //如果收到信号这个函数返回真
 WTERMSIG(wstatus)    //获取信号号    

pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:指定回收的子进程资源
参数:
    @pid:进程号
        > 0   回收这个指定pid进程的资源
        -1    回收任意子进程的资源  wait(NULL) == waitpid(-1,NULL,0);
         0    回收同组的任意子进程的资源
	    <-1   首先对这个值取绝对值,和这个绝对值同组的进程的资源都可以被回收         
 @wstatus:子进程退出的状态
 @options:标志位
	    0:阻塞回收资源
        WNOHANG:非阻塞回收资源
返回值:成功返回后回收掉资源的子进程的pid
       失败返回-1,置位错误码

wait代码如下(示例):

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc,const char * argv[])
{
    pid_t pid;

    pid = fork();
    if (pid == -1) {
        PRINT_ERR("fork error");
    } else if (pid == 0) {
        printf("子进程\n"); //会显示
        printf("******************\n"); //会显示
        exit(25); //进程在这里已经结束了,刷新缓冲区
        
    } else {
        int status;
        printf("父进程\n");
        wait(&status);
        if (WIFEXITED(status)) { //子进程正常退出它为真
            printf("status = %d\n", WEXITSTATUS(status)); //打印子进程退出码 25
        } 
        if(WIFSIGNALED(status)){ //子进程被信号杀死它为真
            printf("signo = %d\n",WTERMSIG(status));  //打印信号号
        }
    }
    return 0;
}

结果展示:
在这里插入图片描述

waitpid代码如下(示例):

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc,const char * argv[])
{
     pid_t pid;

    pid = fork();
    if (pid == -1) {
        PRINT_ERR("fork error");
    } else if (pid == 0) {
        printf("子进程\n"); //会显示
        printf("******************\n"); //会显示
        sleep(3);
        exit(125); //进程在这里已经结束了,刷新缓冲区

    } else {
        int status;
        printf("父进程\n");
        waitpid(pid,NULL,0);
    }
    return 0;
}

结果展示:
在这里插入图片描述
总结:
常见的用法: wait(NULL);
wait(NULL) == waitpid(-1,NULL,0);

三、守护进程

3.1 守护进程的概念

守护进程是一个后台运行的进程,不依附当前的终端,随着系统启动而启动,随着系统的终止而终止,相当于window系统的服务。

3.2 守护进程的创建流程

1.创建出一个孤儿进程(不依赖于终端)
父进程结束子进程没被父进程回收

2.新建一个会话

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

pid_t setsid(void);
功能:创建一个新的会话,如果当前进程不是组长进程,
      创建的会话id,组id就是当前pid的值
参数:
    @无
返回值:成功返回pid,失败返回-1置位错误码

3.将当前进程对应的路径切换到根目录

#include <unistd.h>

int chdir(const char *path);
功能:切换路径的函数
参数:
    @path:路径
返回值:成功返回0,失败返回-1置位错误码

4.设置umask的值

#include <sys/types.h>
#include <sys/stat.h>

mode_t umask(mode_t mask);
功能:设置掩码
参数:
    @mask:掩码的值
返回值:总是会成功,返回mask

5.进行文件描述重定向(脱离终端了,向终端上写东西会有问题)

#include <unistd.h>
dup相当于拷贝,光标互相影响。
int dup(int oldfd);
功能: dup函数的功能,拷贝fd,产生一个新的文件描述符nfd,
       nfd产生的原则最小为使用原则,fd和nfd都可以操作同一个
       文件,fd和nfd共用同一个光标
参数:
    @oldfd:旧的文件描述符
返回值:成功返回nfd,失败返回-1置位错误

重点看dup2
int dup2(int oldfd, int newfd);
功能:dup2函数相当于文件描述符的重定向,
     把newfd重定向到oldfd中了,以后向newfd写内容就是在向odlfd对应的文件中写内容
参数:
    @oldfd:旧文件描述符
    @newfd:新文件描述符
返回值:成功返回newfd,失败返回-1置位错误码

3.3 文件描述重定向实例

dup代码如下(示例):

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)

int main(int argc,const char * argv[])
{
    int fd,nfd;
    if((fd=open("./hi.txt",O_RDONLY))==-1)
    {
        PRINT_ERR("open file error");
    }
    if ((nfd = dup(fd)) == -1)
    {
        PRINT_ERR("dup fd error");
    }
    printf("fd=%d,nfd=%d\n",fd,nfd);
    char ch;
    lseek(fd,3,SEEK_SET);
    read(fd,&ch,1);
    printf("fd=%c\n",ch);
    read(nfd,&ch,1);
    printf("nfd=%c\n",ch);
    return 0;
}

结构展示:
在这里插入图片描述
总结:
dup函数的功能:拷贝fd,产生一个新的文件描述符nfd
nfd产生的原则最小为使用原则,fd和nfd都可以操作hello.txt文件,fd和nfd共用同一个光标

dup2代码如下(示例):

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PRINT_ERR(msg)                                      \
    do                                                      \
    {                                                       \
        printf("%s %s %d\n", __FILE__, __func__, __LINE__); \
        perror(msg);                                        \
        return -1;                                          \
    } while (0)

int main(int argc, const char *argv[])
{
    int fd;
    if ((fd = open("./hi.txt", O_RDWR)) == -1)
    {
        PRINT_ERR("open file error");
    }
    if ((dup2(fd, 1)) == -1)
    {
        PRINT_ERR("dup fd error");
    }
    printf("---------------\n");

    return 0;
}

结构展示:
在这里插入图片描述
总结:
dup2函数相当于文件描述符的重定向,把文件描述符1(标准输出)重定向到fd中了,以后向1写内容就是在向fd对应的文件中写内容。

3.4 创建守护进程实例

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#define PRINT_ERR(msg) \
    do                 \
    {                  \
        printf("%s %s %d\n",__FILE__,__func__,__LINE__);      \
        perror(msg);   \
        return -1;     \
    } while (0)
int main(int argc,const char * argv[])
{
    pid_t pid;
    pid=fork();
    if(pid==-1)
    {
        PRINT_ERR("fork error");
    }
    else if(pid==0)
    {
        //1.创建孤儿进程
        //2.新建会话
        if(setsid()==-1)
        {
            PRINT_ERR("setsid error");
        }
        //3.切换到根目录
        if(chdir("/")==-1)
        {
            PRINT_ERR("chdir error");
        }
        //4.设置掩码
        umask(0);
        //5.关闭其他文件描述符
        //getdtablesize()获取系统中的最大文件描述符个数
        for (int i = 3; i <= getdtablesize(); i++) 
        {
            close(i);
        }
        //6.创建日志文件
        int fd;
        if((fd=open("my.log",O_RDWR|O_CREAT|O_APPEND,0666))==-1)
        {
            PRINT_ERR("open file error");
        }
        //7.文件描述符重定向
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);
        //8.开启自己的服务
        while(1)
        {
            write(fd,"我是夜猫徐\n",strlen("我是夜猫徐\n"));
            sleep(3);
        }
    }
    else
    {
        //创建孤儿进程需要退出父进程
        printf("父进程退出\n");
        exit(EXIT_SUCCESS);
    }
    return 0;
}

结果展示:
sudo ./a.out执行守护进程
在这里插入图片描述
cd /
在这里插入图片描述
vi my.log
在这里插入图片描述


总结

以上就是今天要讲的内容,本文以详细的代码示例介绍了进程相关函数的使用和守护进程的创建方法以父子进程共用光标的问题。如有错误请海涵可以私信博主,望有所收获。

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜猫徐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值