码迷,mamicode.com
首页 > 系统相关 > 详细

多进程编程

时间:2015-10-28 17:16:05      阅读:285      评论:0      收藏:0      [点我收藏+]

标签:

 
 
 
1.多进程
    一个程序的执行活动,就是一个进程,系统为这个进程分配独立的地址空间,资源等等,所以进程事实上就是一个资源的集合体。进程就是为多道编程服务的,通过系统的调度,使得系统可以执行多个进程,使得多个进程看起来都可以同时被系统执行。
 
    多进程编程主要的内容包括进程的控制和进程间的通信。
 
 
 
1.1 进程的控制
    1.1.1 进程的创建
  1. pid_t fork(void)
  2. fork()被调用一次,却返回两次,可能的返回值:
  3. 1.在父进程中,fork 返回新创建的子进程的的 PID
  4. 2.在子进程中,fork 返回零
  5. 3.如果出现错误, fork 返回一个负值
  6. 子进程被创建之后,会从 fork()之后的代码开始运行,而不会重复运行父进程运行过的代码,
  1. int main(int argc,char*argv)
  2. {
  3. pid_t pid;
  4. pid = fork();// 创建进程之后,父子进程,都是从这边开始去执行代码
  5. if(pid <0)
  6. printf("error\n");
  7. elseif(pid ==0)
  8. printf("child PID = %d\n", getpid());
  9. else
  10. printf(" PPID = %d\n", getppid());
  11. return0;
  12. }
    fork() 运行的时候,其实会返回两次数值,父进程返回一次,子进程返回一次。程序运行到 fork() 函数的时候,全部的代码会被共享一份出来,变成两份代码一起运行,也就是说代码会被共享。而数据的部分,是从父进程拷贝一份出来给子进程,也就是说数据是拷贝。之后的运行,各个进程都有自己独立的地址空间和资源,就各归各的,以后他们要进行通信的话,就只能是实现进程间的通信了。
    一般来说,因为对于父进程,fork() 返回的是子进程的 pid,而子进程返回的是零,返回值的不同,所以我们一般都是通过 if/...... less 的方式,实现父子进程执行不同的代码,完成不同的功能。
  1. pid_t vfork(void)也是创建子进程,
区别:
    数据段的区别:
    vfork():子进程与父进程共享父进程的数据段
    fork: 子进程拷贝父进程的数据段
 
    执行数序的区别:
    vfork : 子进程先运行,子进程运行  exec 函数族的时候或者 exit 的时候,才运行父进程
    fork : 父子进程的顺序不确定
 
    vfork 和fork,都创建了进程,但是 vfork 一般的主要目的是 使用  exex 函数族执行其他的程序,实际上,在没有调用 exec 和 exit 子函数之前,子进程与父进程是共享数据段的。vfork 运行,子进程先运行,父进程被挂起,知道子进程调用 exec 或者 exit 。父子进程就不再有限制。
 
    相同:
    两者都是被调用一次,返回两次。子进程的返回值是零,父进程的返回值是子进程的的进程ID,
 
    1.1.2 exec 函数族
    fork 是创建一个新的进程,产生一个新的 PED,但是 exec 则是启动一个新的进程,然后抵换原有的进程,所以进程的 PED 是不会被改变。
  1. int execl(constchar*path,constchar*arg1,,,,)
  2. path :被指定的程序名字,包含完整的路径
  3. arg1 - argn 被指定的程序所需的命令行的参数,以空指针(NULL)结束。
 
  1. int execlp(constchar*path,constchar*arg1,,,,)
  2. path :被执行的程序名,这个是不包含路径的哦,
  3. argv 被指定的命令行参数,还是以空指针,NULL ,结束
 
  1. int execlv(constchar*path,constchar*argv[])
  2. path :被执行的程序名,包含完整的路径
  3. argv 被指定的命令行参数,是包含在数组里面
  1. int system(constchar*string )
  2. 调用 fork 产生子进程,由子进程来调用/bin/sh -c string 来执行 string 所代表的命令
 
 
  
 
 
 
 
 1.1.3 进程等待
pid_t wait(int*status )
阻塞该进程,让它一直等待,知道他的某个子进程退出,
 
 
 
int main(int argc,char**argv)
{
pid_t pc, pr;
// 创建进程
pc = fork();
if(0== pc)
{
printf("这是子进程,进程号是 : %d\n", getpid());
sleep(10);
}
elseif(pc >0)
{// 保证是子进程运行之后,再运行父进程,
pr = wait(NULL);
printf("这是父进程,进程号是 : %d\n", getppid());
}
return0;
}
只要出现了 wait ,那么进程就会被阻塞,直到子进程退出之后,再运行父进程
 
 
 
 
 
1.2 进程间通信
    任务的完成不是由一个单一的进程完成的,所以必须进行通信。
 
通信的方式:
    (1)管道
    (2)信号
    (3)消息队列
    (4)共享内存
    (5)信号量
    (6)套接字
 
1.2.1 管道
    管道是进程间通信最古老的方式,分为无名管道和有名管道。
    
    A. 无名管道
    用于父子进程间的通信,
创建管道:
int pipe(int filedis[2]);
    管道被创建,会返回来两个文件的描述符。 fileis[0] 用于读管道,fileis[1]用于写管道。无名管道被用于父子进程之间的通信,所以,必须是先创建一个管道,然后在 fork 函数创建进程,这样子进程也就会完全进程父进程所创建的管道,所以必须记住,先创建管道,再创建进程。
#define INPUT 1
#define OUTPUT 0
 
void main(){
int file_descriptors[2];
/*定义子进程号 */
pid_t pid;
char buf[256];
int returned_count;
/*创建无名管道*/
pipe(file_descriptors);
/*创建子进程*/
if((pid = fork())==-1){
printf("Error in fork/n");
exit(1);
}
/*执行子进程*/
if(pid ==0){
printf("in the spawned (child) process.../n");
/*子进程向父进程写数据,关闭管道的读端*/
close(file_descriptors[INPUT]);
write(file_descriptors[OUTPUT],"test data", strlen("test data"));
exit(0);
}else{
/*执行父进程*/
printf("in the spawning (parent) process.../n");
/*父进程从管道读取子进程写的数据,关闭管道的写端*/
close(file_descriptors[OUTPUT]);
returned_count = read(file_descriptors[INPUT], buf,sizeof(buf));
printf("%d bytes of data received from spawned process: %s/n",
returned_count, buf);
}
}
    B. 有名管道
    用于任意两个进程之间的通信
int mkfifo(constchar*pathname,mode_t mode)
pathname : FIFO 文件名,实质上,就是文件名嘛。
mode 属性,
实质上,命名管道,就是一个文件,两个进程之间的通信,实质就是通过一个文件,进行通信
    明明管道,本质上就是一个文件,所以创建了文件之后,就可以使用文件的 read write open 对文件进行操作了。
read.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
 
int main()
{
char buf[100];
int fd;
int by;
 
// 创建管道
mkfifo("FIFO", O_CREAT | O_RDONLY);
// 清零
memset(buf,0,sizeof(buf));
// 打开管道,其实就是文件拉,
fd = open("FIFO", O_RDONLY | O_NONBLOCK);
if(fd ==-1)
{
printf(" failed to open\n");
exit(-1);
}
 
while(1)
{// 循环,读取,循环清零
memset(buf,0,sizeof(buf));
if((by = read(fd, buf,100))==-1)
{
printf("no data\n ");
}
printf("read %s \n", buf);
sleep(1);
}
return0;
}
write.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
 
int main(int argc,char*argv)
{
char buf[100];
int fd;
int by;
 
if(argc <2)
{
printf("give moer inf \n");
exit(-1);
}
 
// 清零
memset(buf,0,sizeof(buf));
// 打开管道,其实就是文件拉,
fd = open("FIFO", O_RDONLY | O_NONBLOCK);
if(fd ==-1)
{
printf(" failed to open\n");
exit(-1);
}
 
// 将输入的字符串,拷贝给 buf
strcpy(bufm argv[1]);
 
if((by = write(fd, buf,100))==-1)
{
printf("error\n");
}
else
{
printf("FIFO is %s \n", buf);
}
return0;
}
 
 
1.2.2 信号
信号的机制是 Linux 最为古老的方式,
 
信号的发送:
int kill(pid_t pid,int signo)
kill 可以给自己,也可以给其他的进程发送信号
pid 指定发送信号的进程号,就把 signo 发送给指定的进程
 
int raise(int signo)
raise 只能给进程的自身发送信号,自己给自己发,所以不需要 PID
 
unsignedint alarm(unsignedint seconds)
指定了当经过 seconds 秒之后,就会自己给自己发送一个 SIFALRM 信号
 
int pause(void)
使进程等待,一直等到接收到一个信号为止,才不在等待
 
信号的处理
接收到信号,要么忽略,要么按照指定的函数执行,要么就按照默认的方式去进行处理
 
void(*signal(int signo,void(*func)(int)))(int)
func :
SIG_IGN :忽略信号
SIG_DFL :按照默认的方式去处理
信号处理的函数名:指定函数,传入的参数,就是信号比如 SIGINT
返回值还是一个函数的指针,
void func(int sign_no)
{
if(sign_no == SIGINT)
printf("signal is SIGINT\n");
 
if(sign_no == SIGQUIT)
printf("signal is SIGQUIT\n");
 
}
int main(int argc,char** argv)
{
// 注册信号处理的函数
signal(SIGINT, func);
signal(SIGQUIT, func);
// 注册好之后,会一直等待,知道有参数进来
pause();
return0;
}
 
kill - s +信号+ PID
 
1.2.3 消息队列
和滚到很类似,这个是快被淘汰的方式。
 
1.2.4 共享内存
共享内存是运行在同一台机器上最快的通信的方式,因为数据不需要不同的进程间的复制。本质上,是一个进程创建一个共享的内存,然后其他的进程则对这块内存进行读写。
A. 创建内存
int shmget(key_t key,int size,int shmflg)
key:内存创建的标志,0/ IPC_PRIVATEIPC_PRIVATE表示创建新的共享的内存,
size 创建的大小,以字节为单位
成功创建的话,返回内存的标识符,错误就为-1
    内存的创建和 malloc 非常的类似
 
B. 映射内存
int shmat(int shmid,char*shmaddr,int flag )
shmid :是shmget 函数返回的光纤内存的标识符
flag 一般是零
shmaddr 获取值,获得共享内存的地址,一般为零,让系统分配
成功的话,返回地址,失败的话,返回值为-1
C.解除内存
char *shmdt(char*shmaddr)
char*c_addr,*p_addr;
int shmid;
// 分配内存
shmid = shmget(IPC_PRIVATE,1024, S_IRUSR | S_IWUSR);
 
if(fork())
{// 获得映射的地址
p_addr = shmat(shmid,0,0);
memset(p_addr,0,1024);
strncpy(p_addr, argv[1],1024);
// 等待,子进程结束
wait(NULL);
exit(0);
}
else
{// 保证父进程先执行
sleep(2);
// 获得映射的地址
c_addr = shmat(shmid,0,0)
printf(" get %s \n", c_addr);
}
 
1.2.5 套接字
这个部分,是使用 socket 编程的套接字。
 
 
1.2.6 信号量
5.信号量
    信号量,又叫信号灯,实质上就是一个计数器,与原子的操作类似。用于为多个线程提供共享数据对象的访问。保证了共享资源对象,会被有限调用。
    执行的操作:
    (1)设置信号量,这个信号量的值自己设定,值为最多可以接收访问的最大值,非负值
    (2)当一个线程访问的时候,只要信号量的值不为零,那么就可以被访问,此时,信号量的值就减一,没来一次访问就减一;当信号量的值为零,那么进程就会进入休眠;
    (3)当一个线程访问完毕之后,就要释放信号量,也就是信号量的值就加一,这样以前被休眠的进程,就会被唤醒,
    信号量的操作:
    所以信号量在创建的时候,就必须设置一个非零的初始值,表示同时有几个线程访问该信号量保护起来的共享的资源,当初始值为一的时候,就变成互斥锁了,关于互斥锁的资料,后面补齐。,互斥锁的话,每次只能有一个线程访问资源。
    信号量的API(内核):
1)信号量的初始化
DEFINE_SEMAPHORE(semaphore_lock)// 一步完成信号量的定义和初始化,此时的信号量设置为1,也就是为互斥锁
// 也可以,分开来,不过比较麻烦,不过是一样的作用
staticstruct semaphore sem;// 信号量的定义
sema_init(struct semaphore * sem,int val);// 信号量的初始化
 
2)获得信号量
void down(struct semaphore *sem)// 获得信号量,其实做减一,当为零的时候,进程就好休眠
int down_interruptible(struct semaphore * sem)
int down_trylock(struct semaphore * sem)// 三种获得信号量的区别,这里不做详解
 
3)释放信号量
void up(struct semaphore *sem)// 其实就是做加一的动作,将信号量的值加一
 

多进程编程

标签:

原文地址:http://www.cnblogs.com/qxj511/p/4917710.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!