【Linux】多进程编程

一、进程基础

  • 进程:进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。
  • 进程与程序的区别
  1.     程序时静态的,它是一些保存 在磁盘上得指令的有序集合,没有任何执行的概念。
  2.     进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡。
  • 进程在操作系统中的表示:task_struct, 进程由一个叫task_struct的结构体描述,也叫进程控制块PCB,也就是说linux中的每个进程对应一个task_struct结构体。该结构体记录了进程的一切。
  • Linux进程中的文件:Linux操作系统中每个进程有两个数据结构描叙文件相关信息。
  1. 第一个:fs_struct,它包含此进程当前工作目录和根目录、umask。umask是新文件被 创建的缺省模式,它可以通过系统调用来改变。
  2. 第二个:files_struct,包含此进程正在使用的所有文件的信息。f_mode字段描述该文件是以什么模式创建的:只读、读写、还是只写。f_pos保存文件中下一个读或写将发生的位置。f_inode描叙文件的VFS索引节点,而f_ops是一个例程向量的指针,每个代表一个想施加于文件的操作的函数。

     技术分享

  • 查看进程信息:比如ps等命令

二、进程控制

Linux环境下,有两个基本的操作用于创建和修改进程:函数fork()用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec()用来启动另外的进程以取代当前运行的进程。

2.1 fork系统调用

定义如下:

#include<sys/types.h>
#include<unistd.h>
 pid_t fork(void);

该函数的调用返回两次,在父进程中返回的是子进程的PID,在子进程返回的是0,根据此来判断当前进程是父进程还是子进程。fork调用失败时,返回-1.并设置errno。

fork函数复制当前进程,在task_struct中创建新的进程表项,很多属性复制自父进程,采用“写时复制”的技术,同时,父进程中打开的文件描述符在子进程中也是打开的,计数器加1.

示例代码:来自http://blog.csdn.net/jason314/article/details/5640969

/*
 *  fork_test.c
 *  version 1
 *  Created on: 2010-5-29
 *      Author: wangth
 */
#include <unistd.h>
#include <stdio.h> 
int main () 
{ 
    pid_t fpid; //fpid表示fork函数返回的值
    int count=0;
    fpid=fork(); 
    if (fpid < 0) 
        printf("error in fork!"); 
    else if (fpid == 0) {
        printf("i am the child process, my process id is %d/n",getpid()); 
        printf("我是爹的儿子/n");//对某些人来说中文看着更直白。
        count++;
    }
    else {
        printf("i am the parent process, my process id is %d/n",getpid()); 
        printf("我是孩子他爹/n");
        count++;
    }
    printf("统计结果是: %d/n",count);
    return 0;
}

2.2 exec函数族

系统调用execve()对当前进程进行替换,替换者为一个指定的程序,其参数包括文件名(filename)、参数列表(argv)以及环境变量(envp)。exec函数族当然不止一个,但它们大致相同,在Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp。

一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。

那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话,怎么办呢?那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序:

示例:

char command[256];
void main()
{
        int rtn; /*子进程的返回数值*/
        while(1) {
                /* 从终端读取要执行的命令 */
                printf( ">" );
                fgets( command, 256, stdin );
                command[strlen(command)-1] = 0;
                if ( fork() == 0 ) {
                       /* 子进程执行此命令 */
                       execlp( command, command );
                       /* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/
                       perror( command );
                       exit( errorno );
                }
                else {
                       /* 父进程, 等待子进程结束,并打印子进程的返回值 */
                       wait ( &rtn );
                       printf( " child process return %d/n",. rtn );
                }
        }
}

三、进程通信

我们知道多,进程间的地址空间相对独立。进程与进程间不能像线程间通过全局变量通信。 如果想进程间通信,就需要其他机制。

常用的进程间通信方式有这几种

  • 传统的进程间通信方式:无名管道(pipe)、有名管道(fifo)和信号(signal)
  • System v IPC对象:共享内存(share memory)、消息队列(message queue)和信号灯(semaphore)
  • BSD:套接字(socket)

3.1 管道通信

管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道,一般文件I/O的函数都可以用来操作管道(lseek除外)。

技术分享

示例:父进程向管道中写数据,子进程从管道中读取数据“hello world”

#include <fcntl.h>
#include <stdio.h>
#include "apue.h"
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stropts.h>
#include <sys/mman.h>


int 
main(int argc , char *argv[])
{
    int n,fd[2];
    pid_t pid;
    char line[MAXLINE];
    if(pipe(fd)<0)
        err_sys("pipe err");
    if((pid=fork())<0)
        err_sys("pipe erro");
    else if(pid>0)
        {
            close(fd[0]);
            if(write(fd[1],"hello world \n",13)<0)
                err_sys("write err");
            
        }
    else{
            close(fd[1]);
            if((n=read(fd[0],line,MAXLINE))<0)
                err_sys("read erro");
            if(write(STDOUT_FILENO,line,n)<0)
                err_sys("write erro");
            }
    exit(0);
}

3.2 信号量

推荐博客:http://blog.chinaunix.net/uid-26833883-id-3228615.html

3.3 共享内存

推荐博客:http://blog.chinaunix.net/uid-26833883-id-3230564.html

共享内存的介绍:

  • 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。
  • 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。
  • 由于多个进程共享一段内存,因此也需要依靠某种同步机制。

操作流程:

    <1>创建/打开共享内存

    <2>映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问

    <3>撤销共享内存映射

    <4>删除共享内存对象

相关API:shmget,shmat,shmd,shmctl.

 

3.4 消息队列

 

四、参考

进程基础 http://www.cnblogs.com/vamei/archive/2012/10/09/2715388.html

从程序到进程 http://www.cnblogs.com/vamei/archive/2012/09/20/2694466.html

进程间管道通信 http://blog.chinaunix.net/uid-26833883-id-3227144.html

 

作者西芒xiaoP

出处http://www.cnblogs.com/panweishadow/

若用于非商业目的,您可以自由转载,但请保留原作者信息和文章链接URL。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。