多进程编程

 
 
 
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.  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. 1 int execl(constchar*path,constchar*arg1,,,,)
    2 path :被指定的程序名字,包含完整的路径
    3 arg1 - argn 被指定的程序所需的命令行的参数,以空指针(NULL)结束。

     

 
  1. 1 int execlp(constchar*path,constchar*arg1,,,,)
    2 path :被执行的程序名,这个是不包含路径的哦,
    3 argv :被指定的命令行参数,还是以空指针,NULL ,结束

     

 
  1. 1 int execlv(constchar*path,constchar*argv[])
    2 path :被执行的程序名,包含完整的路径
    3 argv :被指定的命令行参数,是包含在数组里面

     

  1. 1 int system(constchar*string )
    2 调用 fork 产生子进程,由子进程来调用/bin/sh -c string 来执行 string 所代表的命令

     

 
 
 1.1.3 进程等待
pid_t wait(int*status )
阻塞该进程,让它一直等待,知道他的某个子进程退出,
 
 
 
 1 int main(int argc,char**argv)
 2 {
 3     pid_t pc, pr;
 4     // 创建进程
 5     pc = fork();
 6     if(0== pc)
 7     {
 8         printf("这是子进程,进程号是 : %d\n", getpid());
 9         sleep(10);
10     }
11     elseif(pc >0)
12     {// 保证是子进程运行之后,再运行父进程,
13         pr = wait(NULL);
14         printf("这是父进程,进程号是 : %d\n", getppid());
15     }
16     return0;
17 }    

 

只要出现了 wait ,那么进程就会被阻塞,直到子进程退出之后,再运行父进程
 
 
 
 
 
1.2 进程间通信
    任务的完成不是由一个单一的进程完成的,所以必须进行通信。
 
通信的方式:
    (1)管道
    (2)信号
    (3)消息队列
    (4)共享内存
    (5)信号量
    (6)套接字
 
1.2.1 管道
    管道是进程间通信最古老的方式,分为无名管道和有名管道。
    
    A. 无名管道
    用于父子进程间的通信,
1 创建管道:
2 int pipe(int filedis[2]);

 

    管道被创建,会返回来两个文件的描述符。 fileis[0] 用于读管道,fileis[1]用于写管道。无名管道被用于父子进程之间的通信,所以,必须是先创建一个管道,然后在 fork 函数创建进程,这样子进程也就会完全进程父进程所创建的管道,所以必须记住,先创建管道,再创建进程。
 1 #define INPUT 1
 2 #define OUTPUT 0
 3  
 4 void main(){
 5     int file_descriptors[2];
 6     /*定义子进程号 */
 7     pid_t pid;
 8     char buf[256];
 9     int returned_count;
10     /*创建无名管道*/
11     pipe(file_descriptors);
12     /*创建子进程*/
13     if((pid = fork())==-1){
14     printf("Error in fork/n");
15     exit(1);
16 }
17 /*执行子进程*/
18 if(pid ==0){
19     printf("in the spawned (child) process.../n");
20     /*子进程向父进程写数据,关闭管道的读端*/
21     close(file_descriptors[INPUT]);
22     write(file_descriptors[OUTPUT],"test data", strlen("test data"));
23     exit(0);
24 }
25 else{
26     /*执行父进程*/
27     printf("in the spawning (parent) process.../n");
28     /*父进程从管道读取子进程写的数据,关闭管道的写端*/
29     close(file_descriptors[OUTPUT]);
30     returned_count = read(file_descriptors[INPUT], buf,sizeof(buf));
31     printf("%d bytes of data received from spawned process: %s/n",
32     returned_count, buf);
33 }
34 }

 

    B. 有名管道
    用于任意两个进程之间的通信
1 int mkfifo(constchar*pathname,mode_t mode)
2 pathname : FIFO 文件名,实质上,就是文件名嘛。
3 mode :属性,
4 实质上,命名管道,就是一个文件,两个进程之间的通信,实质就是通过一个文件,进行通信

 

    明明管道,本质上就是一个文件,所以创建了文件之后,就可以使用文件的 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 最为古老的方式,
 
信号的发送:
1 int kill(pid_t pid,int signo)
2 kill 可以给自己,也可以给其他的进程发送信号
3 pid :指定发送信号的进程号,就把 signo 发送给指定的进程

 

 
int raise(int signo)
raise 只能给进程的自身发送信号,自己给自己发,所以不需要 PID

 

 
1 unsignedint alarm(unsignedint seconds)
2 指定了当经过 seconds 秒之后,就会自己给自己发送一个 SIFALRM 信号
3  
4 int pause(void)
5 使进程等待,一直等到接收到一个信号为止,才不在等待
6  
7 信号的处理
8 接收到信号,要么忽略,要么按照指定的函数执行,要么就按照默认的方式去进行处理

 

 
 1 void(*signal(int signo,void(*func)(int)))(int)
 2 func :
 3 SIG_IGN :忽略信号
 4 SIG_DFL :按照默认的方式去处理
 5 信号处理的函数名:指定函数,传入的参数,就是信号比如 SIGINT
 6 返回值:还是一个函数的指针,
 7 void func(int sign_no)
 8 {
 9 if(sign_no == SIGINT)
10 printf("signal is SIGINT\n");
11  
12 if(sign_no == SIGQUIT)
13 printf("signal is SIGQUIT\n");
14  
15 }
16 int main(int argc,char** argv)
17 {
18 // 注册信号处理的函数
19 signal(SIGINT, func);
20 signal(SIGQUIT, func);
21 // 注册好之后,会一直等待,知道有参数进来
22 pause();
23 return0;
24 }

 

 
kill - s +信号+ PID
 
1.2.3 消息队列
和滚到很类似,这个是快被淘汰的方式。
 
1.2.4 共享内存
共享内存是运行在同一台机器上最快的通信的方式,因为数据不需要不同的进程间的复制。本质上,是一个进程创建一个共享的内存,然后其他的进程则对这块内存进行读写。
A. 创建内存
int shmget(key_t key,int size,int shmflg)
key:内存创建的标志,0/ IPC_PRIVATE,IPC_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)// 其实就是做加一的动作,将信号量的值加一
 
posted @ 2015-10-28 16:22  qxj511  阅读(186)  评论(0编辑  收藏  举报