前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >进程 · 全家桶

进程 · 全家桶

作者头像
看、未来
发布2020-08-25 14:10:04
3090
发布2020-08-25 14:10:04
举报

文章目录
  • 初见进程,先查一下户口
    • ①进程环境
    • ②进程状态
    • ③进程原语
      • 3.1、fork
      • 进程的产生方式:
      • 3.2、exec族
      • 3.3、wait/waitpid
  • 来个联系方式吧,进程间通信
    • 进程间同步

  • 家庭关系如何?(进程间关系)
    • ①进程组
    • ②会话
  • 守护者
  • 程序、进程与线程的区分
在这里插入图片描述
在这里插入图片描述

初见进程,先查一下户口

①进程环境

别吃惊我为什么能有个圈圈的①,专用符号嘿嘿。

代码语言:javascript
复制
进程控制块PCB:就是进程在操作系统中的“户口”,具体实现是 task_struct数据结构:

1.状态信息,例如这个进程处于可执行状态,休眠,挂起等。

2.性质,由于unix有很多变种,进程有自己独特的性质。

3.资源,资源的链接比如内存,还有资源的限制和权限等。

4.组织,例如按照家族关系建立起来的树(父进程,子进程等)。
②进程状态
代码语言:javascript
复制
运行状态R(TASK_RUNNING)

可中断睡眠状态S(TASK_INTERRUPTIBLE)

不可中断睡眠状态D(TASK_UNINTERRUPTIBLE)

暂停状态T(TASK_STOPPED或TASK_TRACED)

僵死状态Z(EXIT_ZOMBIE)

退出状态X(EXIT_DEAD)

以上两部分,了解即可

③进程原语
3.1、fork
代码语言:javascript
复制
#include <unistd.h>

pid_t fork(void);

功能:子进程复制父进程中的0~3g空间和PCB,但ID号不同。

fork调用一次返回两次 父进程中返回子进程id (就是大于0的意思) 子进程返回0 读时共享写时复制,可保高效

与之相关函数:

代码语言:javascript
复制
#include<sys/types.h>
#include<unistd.h>

pid_t getpid(void); //获取进程ID
pid_t getppid(void); //获取父进程ID
代码语言:javascript
复制
#include <unistd.h>
#include <sys/types.h>

uid_t getuid(void);//返回实际用户ID
uid_t geteuid(void);//返回有效用户ID
进程的产生方式:

进程的产生有多种方式,但是追本溯源是相通的。

代码语言:javascript
复制
(1)复制父进程的系统环境(放心,只要是你开的进程,肯定有父进程)
(2)在内核中建立进程结构
(3)将结构插入到进程列表,便于维护
(4)分配资源给该进程
(5)复制父进程的内存映射消息
(6)管理文件描述符和链接点
(7)通知父进程

下面是一张进程列表的图,命令:pstree。

在这里插入图片描述
在这里插入图片描述

可以看到init是所有进程的父进程,其他进程都是有init进程直接或间接fork出来的。

3.2、exec族

为什么需要exec函数?

fork子进程是为了执行新程序(fork创建了子进程后,子进程和父进程同时被OS调度执行,因此子进程可以单独的执行一个程序,这个程序宏观上将会和父进程程序同时进行)

可以直接在子进程的if中写入新程序打代码。但这样不够灵活,因为我们只能把子进程程序的源代码贴过来执行(必须知道源代码,而且源代码太长了也不好控制) 使用exec族函数运行新的可执行程序。exec族函数可以直接把一个编译好的可执行程序直接加载运行。

有了exec族函数后,典型打父子进程程序是这样的:子进程需要运行的程序被单独编写、单独编译链接成一个可执行程序(hello)。主进程为父进程,fork创建了子进程后在子进程中exec来执行hello,达到父子进程分别做不同程序同时(宏观上)运行的效果。

在我的印象中,我有一篇博客专门讲解exec族,就那么一找,还真有:exec族

代码贴这儿,可以进那篇看更详细,也可以在这里看:

代码语言:javascript
复制
#include<unistd.h>

int execve(const char *path,char *const argv[],char *const envp[]);//这个是真正的系统调用
//以下的函数最后都是调用这个函数

int execl(const char *path,char *const argv,···);
int execlp(const char *file,char *const argv,···);
int execle(const char *path,char *const argv,···· ,char *const envp[]);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv,);
代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int main(void)
{
    pid_t pid;
    if((pid = fork()) < 0)
    {
        perror("fork");
        exit(1);
    }
    else if(pid == 0)
    {
        /* child */
        execl("/bin/ls", "ls", "-l", "-a",NULL);
    }
    else
    {
        printf("parent, child id = %d.\n",pid);
    }
    return 0;
}
3.3、wait/waitpid

这里几个概念:

僵尸进程:子进程退出,父进程没有及时回收,子进程成为僵尸进程 孤儿进程:父进程退出,而子进程没有退出,子进程成为孤儿进程 init进程:1号进程,负责收留孤儿进程,成为他们的父进程

代码语言:javascript
复制
有5种方式终止进程:
(1)main返回
(2)调用exit
(3)调用_exit
(4)调用abort(给自己发送异常终止信号)
(5)由一个信号终止
代码语言:javascript
复制
#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int *status);
//这里的status为一个整形指针,是该子进程的返回状态。若该指针不为空,则可以通过该指针获取子进程退出时的状态。

pid_t waitpid(pid_t pid,int *status,int options);
// pid是进程号
/*
	<-1 回收指定进程组内的任意子进程
	-1 回收任意子进程
	0 回收和当前waitpid调用一个组的所有子进程
	>0 回收指定ID的子进程
*/
//options:
/*
WNOHANG:强制回收,不阻塞。
WUNTRANCED:一般用上面那个
*/

来个联系方式吧,进程间通信

常用的通信方式有:管道、消息队列、共享内存、文件空间映射。

管道:两个进程间通信,最古老的通信方式了。

消息队列:在内核中建立一个链表,发送方按照一定标识将数据发送到内核中,内核将其放入链表。 ()接收方发送请求后,内核按照标识取出消息。 ()消息队列是一种完全异步的通信方式。

共享内存:共享内存是将内存中的一段地址,在多个进程间共享。多个进程通过挂载在自己名下的地址直接对共享内存进行操作。

文件空间映射:mmap函数用来将文件或设备空间映射到内存中,可以通过对映射后的内存空间操作来获得与操作文件一致的效果。

这块如果要展开的话,篇幅会很长,很长,所以我做了一个目录表: 想要进程的联系方式?点这里

进程间同步

进程间同步的方法主要有system信号量和进程间锁,信号量我会在后面的文章再行整理,进程间锁嘛, 进程间锁

家庭关系如何?(进程间关系)

①进程组

一个或多个进程组成的集合,进程组的组ID是一个正整数。

代码语言:javascript
复制
//获取当前进程组组ID

pid_t getpgid(pid_t pid);
pid_t getpgrp(void);

几个概念: 组长进程:进程ID号等于组ID。 组长进程可以创建一个进程组,创建该进程组中的进程。 只要进程中有一个进程存在,进程组就存在,与组长进程是否终止无关。 进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)

代码语言:javascript
复制
//一个进程可以为自己或子进程设置进程组ID

int setpgid(pid_t pid,pid_t pgid);

//非root进程只能改变自己创建的子进程,或有权限操作的进程
②会话
代码语言:javascript
复制
pid_t setsid(void);

1、调用进程不能是进程组组长,该进程变成新会话的首进程。 2、该进程成为一个新进程组的组长进程。 3、需要有root权限(ubunt不需要) 4、新会话丢弃原有控制终端,该会话没有控制终端。 5、建立新会话时,先用fork,然后父进程终止,子进程调用 。

代码语言:javascript
复制
pid_t getsid(pid_t pid);

用于查看当前进程的会话ID

注意点:组长进程不能成为新会话首进程,新会话首进程必定成为组长进程。

来串伪代码:

代码语言:javascript
复制
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>

int main()
{
	pid_t pid;
	pid = fork();

	if(pid == 0)
	{
		//打印:
		getpid();//进程ID
		getpgid(0);//组ID
		getsid(0);//会话ID
		
		sleep(10);
		
		setsid();//子进程设为会话组长
		//子进程非组长进程,故其成为新会话首进程,且成为组长进程。
		//该进程ID即为会话进程ID
		
		//再打印一遍
		getpid();//进程ID
		getpgid(0);//组ID
		getsid(0);//会话ID
	}
}

守护者

守护进程

这篇我还想留着呢,所以就贴个链接吧,删了怪可惜的。

程序、进程与线程的区分

这个问题老师问过我,当时我没答上来。

代码语言:javascript
复制
(1)进程是动态的,程序是静态的。
(2)一个进程只能对应一个程序,而一个程序可以对应多个进程。

从操作系统方面来看,进程代表的是操作系统分配的内存、CPU时间片等资源的基本单位,是为正在运行的程序准备的运行环境。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 初见进程,先查一下户口
    • ①进程环境
      • ②进程状态
        • ③进程原语
          • 3.1、fork
          • 3.2、exec族
          • 3.3、wait/waitpid
      • 来个联系方式吧,进程间通信
      • 家庭关系如何?(进程间关系)
        • ①进程组
          • ②会话
          • 守护者
          • 程序、进程与线程的区分
          相关产品与服务
          消息队列 CMQ 版
          消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档