11.进程关系和进程间通信


11.1.进程状态和system函数和进程关系
(1)进程的5种状态;就绪态-该进程当前所有运行条件就绪,只要得到了CPU时间就能直接运行;运行态-就绪态时得到了CPU运行时间就进入运行态开始运行;僵尸态-子进程已经结束但是父进程还没来得及回收子进程的尸体;等待态(浅度睡眠&深度睡眠)-该进程在等待某种条件,条件成熟后可进入就绪态,等待态下即使给CPU运行时间调度该进程也无法执行,浅度睡眠等待时进程可以被唤醒(信号/资源就位),而深度睡眠等待时进程不能被唤醒只能等待的条件到了才能结束睡眠状态;暂停态-暂停并非该进程的终止,该进程只是被人暂停了(信号),还可以恢复(信号)(见图1)。
(2)system函数;效果等同于fork+exec;其是原子操作,即整个操作一旦开始即不被打断的执行完,原子操作的好处即不会被人打断(不会引来竞争状态),坏处是自己单独连续占用CPU时间太长影响系统整体实时性,则应尽量避免不必要的原子操作,即使不得不原子操作也应该尽量缩短原子操作的时间。
(3)进程关系;无关系-两个进程完全独立,操作系统中的大部分进程彼此之间都是没有关系的,即进程里面的代码不能随便访问别的进程;父子进程关系-两个进程存在父子关系,此时两个进程间联系紧密,子进程继承了父进程fork之前的所有操作;进程组(group)-由若干进程构成1个进程组,操作系统为了方便管理进程,将关系紧密的多个进程构成1个进程组;会话(session)-会话就是进程组的组,由多个进程组构成的组,同样是为了方便管理进程。


11.2.守护进程的引入
(1)进程查看命令ps;ps-ajx(显示操作系统所有进程的各种有关的ID号);ps-aux(显示操作系统所有进程的各种占用资源);向进程发送信号指令kill;kill-信号编号_进程ID(向某个进程发送某个信号);kill-9_xxx(向xxx进程发送9号信号即结束进程)。
(2)守护进程即daemon,简称为d,进程名后面带d的基本就是守护进程;守护进程是长期运行的,一般开机运行直到关机时结束运行;守护进程与控制台脱离,普通进程都和运行该进程的控制台相绑定,即若终端被强制关闭则该终端中运行的所有进程都被会关闭,因为该终端下的所有进程隶属于同一个控制台会话。
(3)服务器(Server)程序即某个始终在运行的程序,可以给我们提供某种服务(譬如nfs服务器给我们提供nfs通信方式),当我们程序需要某种服务时我们可以调用服务器程序(通过和服务器通信得到服务器程序的帮助)来进行服务操作,服务器程序普遍都实现为守护进程。
(4)常见守护进程;syslogd-系统日志守护进程,提供syslog功能(ps-aux|grep”syslogd”);cron-实现操作系统的时间管理,linux中实现定时执行程序的功能就要用到cron。
(5)任何一个进程都可以将自己实现成守护进程;CreateDaemon函数要素-子进程等待父进程退出+子进程使用setsid创建新的会话期以脱离控制台+调用chdir将当前工作目录设置为/根目录+umask设置为0以取消任何文件权限屏蔽+关闭所有文件描述符+将0、1、2定位到/dev/null回收站。


11.3.使用syslog记录调试信息
(1)我们可通过openlog/syslog/closelog这3个函数使用syslog日志记录进程在运行过程中产生的调试信息;log信息普遍都在操作系统的/var/log/messages文件中存储,但ubuntu中是在/var/log/syslog文件中存储。
(2)操作系统中有个守护进程syslogd(开机运行,关机时才结束),该守护进程syslogd负责进行日志文件的写入和维护;syslogd是独立于我们任何进程而运行的,我们当前进程和syslogd进程本来是没有任何关系,但我们当前进程可以通过调用openlog打开和syslogd相连接的通道,然后通过syslog向syslogd发消息,然后由syslogd来将其写入到日志文件系统中。
(3)syslogd其实是1个日志文件系统的服务器进程,提供日志服务,任何需要写日志的进程都可以通过openlog/syslog/closelog这3个函数来利用syslogd提供的日志服务,次即操作系统的服务式的设计。


11.4.让程序不能被多次运行
(1)因为守护进程是长时间运行而不退出,因此./a.out执行1次就有1个进程,执行多次就有多个进程;我们守护进程一般都是服务器,服务器程序只要运行1个就够了,多次同时运行并没有意义甚至会带来错误;因此我们希望程序具有单例运行的功能,即当我们./a.out去运行程序时,如果当前还没有该程序的进程运行则运行之,如果之前已经有该程序的进程在运行则本次运行直接退出(提示程序已经在运行)。
(2)最常用的方法是用某个文件的存在与否来做标志,程序在执行之初去判断某个特定的文件是否存在,若存在则表明进程已经在运行,若不存在则表明进程没有在运行,然后运行程序时去创建该文件,当程序结束的时候去删除该文件即可,该特定文件要古怪一点,确保不会凑巧真的在电脑中存在。


11.5.linux进程间通信概述
(1)进程间通信IPC指的是2个任意进程之间的通信;某个进程在相同的地址空间中,则该进程内的不同模块(不同函数/不同文件)之间的通信都是很简单的,可通过全局变量/函数形参实参进行通信;但2个不同的进程处于不同的地址空间,因此要互相通信很难。
(2)99%的程序是不需要考虑进程间通信的,因为现在大部分程序都是单进程的,它们普遍采用多线程解决并发问题;复杂/大型的程序中因为设计的需要被设计成多进程程序,即整个程序被设计成多个进程同时工作来完成的模式,常见的如GUI体系/服务器;所以IPC技术在一般中小型程序中用不到,在大型程序中才会用到。
(3)linux内核提供多种进程间通信机制;无名管道和有名管道(最早出现的提供父子进程之间的通信);信号量+消息队列+共享内存(由SystemV_IPC_Unix发展而来);Socket域套接字(网络通信,网络上两台不同电脑上不同进程之间的通信,由BSD_Unix发展而来);信号。
(4)我们为什么不详细研究IPC;日常使用少,只有大型程序才能用上(譬如阿里云的服务器程序底层开发);学习更为复杂,属于linux应用编程中难度最大的部分;细节多,涉及到的函数和参数细节非常多;在面试较少涉及,对找工作帮助不大;建议后续深入学习时再来实际写代码详细探讨。


11.6.有名管道和无名管道
(1)管道(默认无名管道)-两个进程通过某块在内核中维护的的内存进行通信;管道有读端和写端并且为防止读写冲突必须被设置成是单工通信;可建立两条单工管道实现双工通信。
(2)管道通信的方法-父进程先创建管道然后fork子进程,子进程继承父进程的管道fd,然后在父进程中关掉读/写端,然后在子进程中关掉写/读端即可。
(3)管道通信的限制-只能在父子进程间通信+单条管道只能实现单工通信;管道通信的函数-pipe(创建管道)/write(写管道)/read(读管道)/close(关闭管道)。
(4)有名管道(fifo)-两个进程通过某块在内核中维护的的内存进行通信,表现形式为1个有名字的文件;有名管道的使用方法-先固定1个文件名,2个进程分别使用mkfifo创建fifo文件,然后分别open打开获取到fd,然后1个读1个写。
(5)有名管道通信限制-单条管道只能实现单工通信(注意不限父子进程,任意2个进程都可);有名管道通信的函数-mkfifo(创建fifo文件)/open/write/read/close。


11.7.SystemV IPC介绍
(1)SystemV_IPC的基本特点-系统通过一些专用API来提供SystemV_IPC功能;共分为信号量+消息队列+共享内存;其实质也是通过内核提供的公共内存进行通信的。
(2)消息队列-本质上是1个队列,队列可以理解为FIFO,该FIFO由内核维护;工作时A和B2个进程进行通信,A向队列中放入消息,B从队列中读出消息(用于实现广播/单播)。
(3)信号量-实质就是个计数器,本质即1个可以用来计数的变量,可理解为1个int类型的变量;通过计数值来提供互斥和同步(用于实现互斥和同步)。
(4)共享内存-内核中大片内存直接映射到两个不同的进程,解决大容量/快速的信息交换;a进程用于摄像头采集,b进程用于视频的编码,c进程用于将编码数据发送到网络,则a进程和b进程共同映射到内核中的同一块内存进行数据的采集和编码工作,类似于LCD显示时的显存用法(用于两进程间大量的数据交换)。


这里写图片描述


11.system
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:进程关系和进程间通信
 * 功能:演示system函数的简单使用。  
 */

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int ret = -1;
    ret = system("ls -la");
    if (ret < 0)
    {
        perror("system error");
        exit(-1);
    }

    return 0;
}

11.daemon
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:进程关系和进程间通信
 * 功能:创建守护进程。   
 */

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

// 函数声明
void CreatDaemon(void);

int main(int argc, char **argv)
{
    CreatDaemon();

    while (1)
    {
        printf("I am running.\n");
        sleep(1);
    }

    return 0;
}

// 把调用该函数的进程变成1个守护进程
void CreatDaemon(void)
{
    pid_t pid = -1;

    pid = fork();

    if (pid > 0)
    {
        exit(0);        // 父进程直接退出
    }
    else if (0 == pid)
    {
        // setsid将当前进程设置为1个新的会话期session,即将当前进程脱离控制台
        pid = setsid();
        if (pid < 0)
        {
            perror("setsid error");
            exit(-1);
        }

        // 将当前进程工作目录设置为根目录
        chdir("/");

        // umask设置为0确保将来进程有最大的文件操作权限
        umask(0);

        // 关闭所有文件描述符
        // 获取当前系统中所允许打开的最大文件描述符数目
        int cnt = sysconf(_SC_OPEN_MAX);
        int i = 0;
        for (i=0; i<cnt; i++)
        {
            close(i);
        }

        // 将0、1、2定位到/dev/null回收站
        open("/dev/null", O_RDWR);
        open("/dev/null", O_RDWR);
        open("/dev/null", O_RDWR);
    }
    else    
    {
        perror("fork error");
        exit(-1);
    }
}

11.syslog
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:进程关系和进程间通信
 * 功能:演示syslog日志服务的使用。  
 */

#include <stdio.h>
#include <syslog.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char **argv)
{
    printf("my pid = %d.\n", getpid());

    // 打开和syslogd相连接的通道;程序名标志为a.out;
    // 若信息无法写到log中则将信息直接发送到控制台;
    // 信息中包含进程PID号;信息级别处于普通用户级别
    // 在ubuntu中使用cat /var/log/syslog查看syslog信息
    openlog("a.out", LOG_CONS | LOG_PID, LOG_USER);

    // 向log日志写入信息;信息是提示信息
    syslog(LOG_INFO, "My name is Rston, The time of now is %ld.\n", time(NULL));

    // 关闭和syslogd连接的通道
    closelog();

    return 0;
}

11.single_run
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:进程关系和进程间通信
 * 功能:演示单例运行程序功能的实现。    
 */

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

#define PATH "./RstonSingleRun"

// 进程退出函数声明
void delete(void);

int main(int argc, char **argv)
{
    int fd = -1;
    // 若文件不存在,则创建,若文件存在,则报错
    fd = open(PATH, O_RDWR | O_CREAT | O_EXCL, 0664);
    if (fd < 0)
    {
        if (errno == EEXIST)
        {
            printf("The process is now running, plsest not repeat run process.\n");
            return -1;
        }
    }

    // 注册进程退出处理函数用于删除特殊文件
    atexit(delete);

    int i = 0;
    for (i=0; i<20; i++)
    {
        printf("I am running now.\n");
        sleep(1);
    }

    return 0;
}

// 进程退出之前删除特殊文件
void delete(void)
{
    remove(PATH);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值