当前位置: 首页 > news >正文

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

文章目录

  • 前言
  • 一、父子进程共用光标问题
    • 1.1 验证
    • 1.2 规避共用光标问题使用多进程拷贝同一个文件
  • 二、进程相关函数
    • 2.1 getpid/getppid函数
    • 2.2 exit/_exit函数
    • 2.3 wait/waitpid函数
  • 三、守护进程
    • 3.1 守护进程的概念
    • 3.2 守护进程的创建流程
    • 3.3 文件描述重定向实例
    • 3.4 创建守护进程实例
  • 总结


前言

前文【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
在这里插入图片描述


总结

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

相关文章:

  • three.js入门 1 介绍和基础代码
  • pytorch-实现天气识别
  • Unity2D学习———角色移动两种方式+小怪追随+Unity演示+C#代码
  • 拓展上机-3题解:哥德巴赫猜想
  • 如果你看不懂别人画的 UML 类图,看这一篇文章就够了
  • 【论文】论文阅读记录
  • 【第三十九讲】Boot 启动流程
  • ApkScan-PKID 查壳工具下载使用以及相关技术介绍
  • 从BNB遭黑客攻击(跨链桥BSC Token Hub遭到攻击),看公链中心化问题
  • 【多线程实践】一、为何使用多线程三种线程创建方式利弊分析
  • LIFELONG LEARNING WITH DYNAMICALLY EXPANDABLE NETWORKS论文阅读+代码解析
  • 计算机网络——集线器与交换机
  • 用通俗易懂的方式讲解:CatBoost 算法原理及案例
  • 系统架构演变和SpringCloud的定义:
  • 后端必会的前端vue基础知识
  • Angular Elements 及其运作原理
  • ECMAScript入门(七)--Module语法
  • Git初体验
  • TypeScript实现数据结构(一)栈,队列,链表
  • ubuntu 下nginx安装 并支持https协议
  • vue 配置sass、scss全局变量
  • Web Storage相关
  • 力扣(LeetCode)56
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 微信小程序:实现悬浮返回和分享按钮
  • 详解移动APP与web APP的区别
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • raise 与 raise ... from 的区别
  • 阿里云服务器购买完整流程
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • #100天计划# 2013年9月29日
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • #微信小程序:微信小程序常见的配置传值
  • %3cli%3e连接html页面,html+canvas实现屏幕截取
  • (1)(1.9) MSP (version 4.2)
  • (12)目标检测_SSD基于pytorch搭建代码
  • (16)Reactor的测试——响应式Spring的道法术器
  • (ibm)Java 语言的 XPath API
  • (zhuan) 一些RL的文献(及笔记)
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • (转)Android学习笔记 --- android任务栈和启动模式
  • ./configure,make,make install的作用(转)
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .NET delegate 委托 、 Event 事件
  • .NET Framework与.NET Framework SDK有什么不同?
  • .net 获取url的方法
  • .NET 设计一套高性能的弱事件机制
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .net分布式压力测试工具(Beetle.DT)
  • ::before和::after 常见的用法
  • :not(:first-child)和:not(:last-child)的用法