Linux系统编程(四)进程

一、进程的产生(fork)

fork(2) 系统调用会复制调用进程来创建一个子进程,在父进程中 fork 返回子进程的 pid,在子进程中返回 0。

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

pid_t fork(void);

fork 后子进程不继承未决信号和文件锁,资源利用量清 0。 由于进程文件描述符表也继承下来的,所以可以看到父子进程的输入输出指向都是一样的,这个特性可以用于实现基本的父子进程通信。

init() 是所有进程的祖先进程,pid = 1。

例子(fork_test.c)

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

int main()
{
  pid_t pid;
  printf("Begin\n");
  //fflush(); //!!!重要

  if ((pid = fork()) == 0) {
    // child
    printf("child process executed\n");
    exit(0);
  } else if (pid < 0) {
    perror("fork");
    exit(1);
  }

  // father
  //sleep(1);
  printf("parent process executed\n");
  exit(0);
}

运行结果

注意:父子进程的运行顺序不能确定,由调度器的调度策略决定。 

面试题:当将输出重定向到文件里面时,Begin 为什么打印了两次?如下图:

答案:输出到终端默认是行缓冲模式,加 “\n” 即可刷新缓冲区,但由于重定向到文件是写文件,而写文件是全缓冲,所以 “\n” 无法刷新缓冲区,所以需要在 Begin 后加上 fflush() 来强制刷新缓冲区。

例子(primes_fork.c,通过子进程来计算质数): 

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

int main()
{
  int max = 100;
  pid_t pid;

  for (int i = 2; i <= max; i++) {
    if ((pid = fork()) == 0) {
      // child
      int flag = 1;
      for (int j = 2; j <= i / 2; j++) {
        if (i % j == 0) {
          flag = 0;
          break;
        }
      }

      if (flag) {
        printf("%d\n", i);
      }

      exit(0);
    } else if (pid < 0) {
      perror("fork");
      exit(1);
    }
  }
  exit(0);
}

通过 man ps 可以找到进程的所有状态信息:

  • D:不可中断的睡眠态(通常是 IO);
  • I:空闲的内核线程;
  • R:运行态或可运行态;
  • S:可中断的睡眠态(等待事件的完成);
  • T:被控制信号停止;
  • X:死亡态;
  • Z:僵尸(zombie)进程,已终止但未被其父亲接收;

其中父进程如果不使用 waitpid 接收子进程状态,会导致子进程终止后变成僵尸态,会占用 pid 号,父进程终止后内核会自动将子进程交付给 init 进程,等待子进程终止后为其 “收尸”。

二、进程的消亡及释放资源(wait、waitpid)

 wait(2) 和 waitpid(2) 可以等待进程状态发生变化。

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

pid_t wait(int *wstatus);

pid_t waitpid(pid_t pid, int *wstatus, int options);

wait(2) 成功时返回终止的子进程的 pid,不需要指定特定的子进程 pid,并且需要死等(阻塞)。 若 wstatus 非空,则其可以一些宏函数指示进程的状态:

  • WIFEXITED(wstatus):若子进程正常终止则返回真(exit(3)、_exit(2) 或从 main 函数返回);
  • WEXITSTATUS(wstatus):返回子进程的退出状态码,前置条件是 WIFEXITED(wstatus) 必须首先为真;
  • WIFSIGNALED(wstatus):若子进程被信号终止了则返回真;
  • WTERMSIG(wstatus):检测终止子进程的信号值,前置条件是 WIFSIGNALED(wstatus) 为真;

waitpid(2) 相比于 wait(2) 可以指定等待的子进程(pid),并且可以指定一些选项(options):

  • WNOHANG:如果没有子进程退出则立即返回(非阻塞)

进程分配任务的方法:

  1. 分块(每个线程一部分任务);
  2. 交叉分配(依次给每个线程分配任务);
  3. 池(往任务池里面扔任务,线程从池中抢任务);

三、exec 函数族

 exec 函数族可以用来执行一个二进制可执行文件。

#include <unistd.h>

extern char **environ;

/* 需要给出文件路径 */
int execl(const char *pathname, const char *arg, ...
                       /* (char  *) NULL */);
/* 只需要文件名称,然后去环境变量environ中寻找 */
int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
int execle(const char *pathname, const char *arg, ...
                       /*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);

exec 函数族会将当前进程映像替换为新的进程映像。所以在 exec 后的代码不会执行。

在 exec 之前需要 fflush(),和前面 1.1 的例子一样,写文件是全缓冲,会导致打印的内容还没写入到文件就被 exec 替换掉了进程映像。

例子,使用 fork + exec 来实现一个简单的 shell(myshell.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <glob.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#define DELIMS " \t\n"

struct cmd_st
{
  glob_t globres;
};

static void prompt(void)
{
  printf("mysh$ ");
}

static void parse(char *line, struct cmd_st *cmd)
{
  char *tok;

  int i = 0;
  while (1) {
    tok = strsep(&line, DELIMS);
    if (tok == NULL)
      break;
    if (tok[0] == '\0') // empty str
      continue;

    glob(tok, GLOB_NOCHECK | GLOB_APPEND * i, NULL, &cmd->globres);
    i = 1;
  }
}

int main()
{
  char *linebuf = NULL;
  size_t linebuf_size = 0;
  struct cmd_st cmd;
  pid_t pid;

  while (1) {
    prompt(); // 打印提示符

    if (getline(&linebuf, &linebuf_size, stdin) < 0) {
      break;
    }

    parse(linebuf, &cmd); // 解析命令

    /* extern cmd */
    {
      pid = fork();
      if (pid < 0) {
        perror("fork");
        exit(1);
      }

      /* child process */
      if (pid == 0) {
        execvp(cmd.globres.gl_pathv[0], cmd.globres.gl_pathv);
        perror("exec");
        exit(1);
      }

      wait(NULL);
    }
  }
  exit(0);
}

可以在 /etc/passwd 文件里修改用户的登录 shell,十分有趣: 

四、守护进程

持续运行在后台,等待处理请求的进程。一次成功的登录会产生一个会话(session)

管道符:把第一个命令的标准输出作为第二个命令的标准输入(ls | more)。

Linux--setsid() 与进程组、会话、守护进程

例子(mydaemon.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FILENAME "/tmp/out"

static void daemonize(void)
{
  pid_t pid;
  int fd;;
  if ((pid = fork()) < 0) {
    perror("fork");
    exit(1);
  }

  if (pid == 0) { // child process
    if ((fd = open("/dev/null", O_RDWR)) < 0) {
      perror("open");
      exit(1);
    }
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    if (fd > 2)
      close(fd);

    setsid();

    // change working directory
    chdir("/"); // preventing "device is busy"
    // umask(0);
    return;
  } else {
    exit(0);
    // the daemon process's parent will be the init process
  }
}

int main()
{
  FILE* fp = NULL;

  // init daemon process
  daemonize();

  // the task of daemon process
  if ((fp = fopen(FILENAME, "w")) == NULL) {
    perror("fopen");
    exit(1);
  }

  for (int i = 0; ; i++) {
    fprintf(fp, "%d\n", i);
    fflush(fp); // writting file is full buffer, so we should flush the buffer after printf()
    sleep(1);
  }

  exit(0);
}

编译运行程序后使用 ps -axj 可以看到 daemon 进程在后台运行,但是发现其 PPID(父进程 pid)不是 init 进程的 pid 1,查了一下发现是在 Ubuntu18.04 系统中,孤儿进程会被 “/lib/systemd/systemd --user” 进程领养。 

pid 为 1097 对应的进程为 /lib/systemd/systemd --user:

syslogd 服务:

  • openlog() 打开系统日志的连接;
  • syslog() 提交日志;
  • closelog() 关闭系统日志的连接;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/388313.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java 调用智谱ai 大模型的完整步骤(国内的 AI 大模型 对话)

要使用java 调用智谱AI的API进行异步调用&#xff0c;您需要遵循以下步骤&#xff1a; 1. **获取API密钥**&#xff1a; - 您需要从智谱AI平台获取一个API密钥&#xff08;API Key&#xff09;&#xff0c;这个密钥将用于所有API请求的身份验证。 2. **SDK源…

使用第三方幻兽帕鲁应用模板部署游戏后,是否需要更新?

需要更新&#xff0c;因为幻兽帕鲁官方客户端更新&#xff0c;所以服务器也需要同步更新&#xff0c;才能继续游玩。版本不一致的话&#xff0c;是不能进入游戏的。 有两种更新方法&#xff1a; 如果你使用幻兽帕鲁应用模板部署游戏&#xff0c;那么可以选择使用游戏配置面板一…

Android14之Android Rust模块编译语法(一百八十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【AI视野·今日CV 计算机视觉论文速览 第298期】Fri, 26 Jan 2024

AI视野今日CS.CV 计算机视觉论文速览 Fri, 26 Jan 2024 Totally 71 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Multimodal Pathway: Improve Transformers with Irrelevant Data from Other Modalities Authors Yiyuan Zhang, Xiaohan …

springboot登录校验

一、登录功能 二、登录校验 2.1 会话技术 2.2 JWT令牌 JWT令牌解析&#xff1a; 如何校验JWT令牌&#xff1f;Filter和Interceptor两种方式。 2.3 过滤器Filter 2.3.1 快速入门 修改上述代码&#xff1a; 2.3.2 详解 2.3.3 登录校验-Filter 2.4 Interceptor拦截器 2.4.1 …

Ps:创建联系表

Ps菜单&#xff1a;文件/自动/联系表 II Automate/Contact sheet II Photoshop 的“联系表 II” Contact Sheet II命令为快速生成图像集合的预览和打印目录提供了一种高效的方法。 此命令可以通过自动化过程读取指定的图像文件&#xff0c;然后根据用户定义的参数&#xff08;如…

【C++关联式容器】unordered_set

目录 unordered_set 1. 关联式容器额外的类型别名 2. 哈希桶 3. 无序容器对关键字类型的要求 4. Member functions 4.1 constructor、destructor、operator 4.1.1 constructor 4.1.2 destructor 4.1.3 operator 4.2 Capacity ​4.2.1 empty 4.2.2 size 4.2.3 max…

人工智能时代

一、人工智能发展历史:从概念到现实 人工智能(Artificial Intelligence,简称AI)是计算机科学领域中一门旨在构建能够执行人类智能任务的系统的分支。其发展历程充满曲折,从概念的提出到如今的广泛应用,是技术、理论和实践相互交织的产物。 1. 起源(20世纪中期) 人工智…

代码随想录算法训练营Day27|回溯算法·组合总和、组合总和II、分割回文串

组合总和 class Solution{ private:vector<vector<int>>result;vector<int>path;void backtracking(vector<int>& candidates,int target,int sum,int startIndex){if(sum > target){return;}if(sum target){result.push_back(path);return;}…

混合键合(Hybrid Bonding)工艺解读

随着半导体技术的持续演进&#xff0c;传统的二维芯片缩放规则受到物理极限的挑战&#xff0c;尤其是摩尔定律在微小化方面的推进速度放缓。为了继续保持计算性能和存储密度的增长趋势&#xff0c;业界开始转向三维集成电路设计与封装技术的研发。混合键合技术就是在这样的背景…

【前端实战小项目】学成在线网页制作

文章目录 1.项目准备1.1 项目目录 2.头部区域2.1 头部区域布局2.2 logo制作2.2 导航制作技巧(nav)2.3搜索区域(search)2.3用户区域(user区域) 3.banner区域3.1 总体布局3.2 左侧侧导航(left)3.3 右侧课程表(left) 4.精品推荐区域(recommend)5.精品课程( course)6.前端开发工程师…

MySQL数据库基础(二):MySQL数据库介绍

文章目录 MySQL数据库介绍 一、MySQL介绍 二、MySQL的特点 三、MySQL版本 四、MySQL数据库下载与安装 1、下载 2、安装 五、添加环境变量&#xff08;Windows&#xff09; 六、检测环境变量是否配置成功 MySQL数据库介绍 一、MySQL介绍 MySQL是一个关系型数据库管理…

【Java多线程案例】定时器

1. 定时器简介 定时器&#xff1a;想必大家一定对定时器这个概念不陌生&#xff01;因为它经常出现在我们的日常生活和编程学习中&#xff0c;定时器就好比是一个"闹钟"&#xff0c;会在指定时间处理某件事&#xff08;例如响铃&#xff09;&#xff0c;而在编程世界…

【微服务】skywalking自定义告警规则使用详解

目录 一、前言 二、SkyWalking告警功能介绍 2.1 SkyWalking告警是什么 2.2 为什么需要SkyWalking告警功能 2.2.1 及时发现系统异常 2.2.2 保障和提升系统稳定性 2.2.3 避免数据丢失 2.2.4 提高故障处理效率 三、 SkyWalking告警规则 3.1 SkyWalking告警规则配置 3.2 …

春节结束后如何收心工作?

一、春节结束后的工作准备 春节假期结束后&#xff0c;迎来了新的工作季。在开始新的工作之前&#xff0c;首先需要对即将展开的工作进行充分的准备。整理和清理工作区域&#xff0c;给自己一个干净整洁的工作环境。检查和更新工作日程&#xff0c;确保未来一段时间的工作规划…

删除 Windows 设备和驱动器中的 WPS网盘、百度网盘等快捷图标

在安装诸如WPS软件、百度云盘、爱奇艺等客户端后&#xff0c;Windows 的“我的电脑”&#xff08;或“此电脑”&#xff09;中的“设备和驱动器”部分会出现对应的软件图标。这种情况被许多技术人员视为不必要的干扰&#xff0c;因此许多用户想要知道如何隐藏或删除这些图标。 …

关于保存int型变量进int型数组的做法

如何保存int型变量进int型数组呢&#xff0c;大家先来看看我写的这串代码&#xff1a; #include <bits/stdc.h>using namespace std; int main(){int n;cin >> n;int num;vector<int>a;for (int i 1;i<n;i){cin >> num;if(num % 2 ! 0){a.push_ba…

装饰工程|装饰工程管理系统-项目立项子系统的设计与实现|基于Springboot的装饰工程管理系统设计与实现(源码+数据库+文档)

装饰工程管理系统-项目立项子系统目录 目录 基于Springboot的装饰工程管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员功能实现 &#xff08;2&#xff09;合同报价管理 &#xff08;3&#xff09;装饰材料总计划管理 &#xff08;4&#xff0…

java排课管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java排课管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&#…

如何将字体添加到 ONLYOFFICE 文档服务器 8.0

作者&#xff1a;VincentYoung 阅读本文&#xff0c;了解如何为自己的在线办公软件 ONLYOFFICE 文档服务器的字体库添加字体 ONLYOFFICE 文档是什么 ONLYOFFICE 文档是一个功能强大的文档编辑器&#xff0c;支持处理文本文档、电子表格、演示文稿、可填写表单、PDF 和电子书…