天天看点

带你搞懂系统编程之--------进程进程

进程

进程通信

共享内存
消息队列
信号量
无名管道
有名管道
Socket

(1) 进程是资源分配的最小单位

父子进程间遵循读时共享写时复制的原则

(2) 进程状态

进程基本的状态有5种。分别为初始态,就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。

带你搞懂系统编程之--------进程进程

(3) linux系统调用函数

头文件:#include〈unistd〉

创建函数 fork() (调用一次返回两次)

等待回收:wait()等待子进程结束

结束:exit()

补充:

system();调用shell命令在当前进程开始进程

exec();调用新路径或新文件的可执行程序来代替原有进程

eg:
   #include <stdio.h>
   #include <unistd.h>
   #include <stdlib.h>
   
  int var = 34;
  
   int main(void)
  {
       pid_t pid;
   /* system("ping www.baidu.com -c 2");
 
   char *args[]={"/bin/ls",NULL};
   printf("系统分配进程号:%d\n",getpid());
 (execve("/bin/ls",args,NULL)<0)
  printf("出错\n");
  */
      pid = fork();
     if (pid == -1 ) {
          perror("fork");
         exit(0);
      }
      else if (pid > 0) {
 
         sleep(2);
          var = 55;
          printf("I'm parent pid = %d, parentID = %d, var = %d\n", getpid(), getppid(), var);
         system("ping www.baidu.com -c 2");
  
         char *args[]={"/bin/ls",NULL};
           printf("系统分配进程号:%d\n",getpid());
          if(execve("/bin/ls",args,NULL)<0)
          printf("出错\n");
       } else if (pid == 0) {
          var = 100;
          printf("child  pid = %d, parentID=%d, var = %d\n", getpid(), getppid(), var);
     }
      printf("var = %d\n", var);
  
     return 0; 
     }
           

进程常用命令:

#ps aux 显示内存中进程

#ps -lA 显示所有进程

#kill 进程id 杀死进程

#top –d 2 每两分钟持续侦测进程状态

#ptree 列出进程树

(4) 僵尸进程与孤儿进程
僵尸进程:

一个父进程利用fork创建子进程,如果子进程退出,而父进程没有利用wait 或者 waitpid 来获取子进程的状态信息,那么子进程的状态描述符依然保存在系统中。

孤儿进程

一个父进程退出, 而它的一个或几个子进程仍然还在运行,那么这些子进程就会变成孤儿进程,孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集的工作

如何避免僵尸进程:

通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收
杀死父进程,变成孤儿进程
           
(5) 进程通信方式

共享内存

共享内存就是允许两个或多个进程共享一定的存储区,就是让两个进程地址通过页表映射到同一片物理地址以便于通信,你可以给一个区域里面写入数据,理所当然你就可以从中拿取数据,这也就构成了进程间的双向通信。

a) 头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

b) 函数:

创建共享内存:shmget()

获取共享内存地址:shmat()

删除共享内存:shmdt()

共享内存控制:shmctl()

/*shmdata.h*/
#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER
#define TEXT_SZ 2048
struct shared_use_st
{  
    int written;//作为一个标志,非0:表示可读,0表示可写 
    char text[TEXT_SZ];//记录写入和读取的文本
};
#endif

/*shmread.c*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include "shmdata.h"
int main()
{  
    int running = 1;//程序是否继续运行的标志  
    void *shm = NULL;//分配的共享内存的原始首地址   
    struct shared_use_st *shared;//指向shm   
    int shmid;//共享内存标识符 //创建共享内存   
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
    if(shmid == -1)
    {      
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }   //将共享内存连接到当前进程的地址空间
    shm = shmat(shmid, 0, 0);//获取共享内存地址
    if(shm == (void*)-1)   
    {  
        fprintf(stderr, "shmat failed\n"); 
        exit(EXIT_FAILURE);
    }  
    printf("\nMemory attached at %X\n", (int)shm);  //设置共享内存   
    shared = (struct shared_use_st*)shm;   
    shared->written = 0;
    while(running)//读取共享内存中的数据 
    {       //没有进程向共享内存定数据有数据可读取       
        if(shared->written != 0)
        {      
            printf("You wrote: %s", shared->text);      
            sleep(rand() % 3);          //读取完数据,设置written使共享内存段可写
            shared->written = 0;         //输入了end,退出循环(程序)  
            if(strncmp(shared->text, "end", 3) == 0)    
                running = 0;       
        }      
        else//有其他进程在写数据,不能读取数据     
            sleep(1);  
    }   //把共享内存从当前进程中分离
    if(shmdt(shm) == -1)   
    {      
        fprintf(stderr, "shmdt failed\n");     
        exit(EXIT_FAILURE);
    }   //删除共享内存   
    if(shmctl(shmid, IPC_RMID, 0) == -1)   
    {  
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");  
        exit(EXIT_FAILURE);
    }  
    exit(EXIT_SUCCESS);
}

/*shnwrite.c*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include "shmdata.h"
int main()
{  
    int running = 1;//程序是否继续运行的标志  
    void *shm = NULL;//分配的共享内存的原始首地址   
    struct shared_use_st *shared;//指向shm   
    int shmid;//共享内存标识符 //创建共享内存   
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);//创建共享内存,IPC_CREAT如果内核不存在则创建它
    if(shmid == -1)
    {      
        fprintf(stderr, "shmget failed\n");
        exit(EXIT_FAILURE);
    }   //将共享内存连接到当前进程的地址空间
    shm = shmat(shmid, 0, 0);
    if(shm == (void*)-1)   
    {  
        fprintf(stderr, "shmat failed\n"); 
        exit(EXIT_FAILURE);
    }  
    printf("\nMemory attached at %X\n", (int)shm);  //设置共享内存   
    shared = (struct shared_use_st*)shm;   
    shared->written = 0;
    while(running)//读取共享内存中的数据 
    {       //没有进程向共享内存定数据有数据可读取       
        if(shared->written != 0)
        {      
            printf("You wrote: %s", shared->text);      
            sleep(rand() % 3);          //读取完数据,设置written使共享内存段可写
            shared->written = 0;         //输入了end,退出循环(程序)  
            if(strncmp(shared->text, "end", 3) == 0)    
                running = 0;       
        }      
        else//有其他进程在写数据,不能读取数据     
            sleep(1);  
    }   //把共享内存从当前进程中分离
    if(shmdt(shm) == -1)   
    {      
        fprintf(stderr, "shmdt failed\n");     
        exit(EXIT_FAILURE);
    }   //删除共享内存   
    if(shmctl(shmid, IPC_RMID, 0) == -1)   
    {  
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");  
        exit(EXIT_FAILURE);
    }  
    exit(EXIT_SUCCESS);
}
           

消息队列

消息队列是内核地址空间中的内部链表,通过linux内核在各进程之间传递内容。将消息写入消息队列,然后再从消息队列中取消息,一般来说是先进先出的顺序,每个消息都有唯一的IPCb标识符。可以解决两个进程的读写速度不同(处理数据速度不同),系统耦合等问题,而且消息队列里的消息哪怕进程崩溃了也不会消失。

a) 头文件

#include<linux/msg.h>

#include<sys/types.h>

#include<sys/ipc.h>

b) 函数

键值构建:ftok() 将路径名转化为IPC键值

获得消息:msgget()

发送消息:msgsnd()

接受消息:msgrcv()

消息控制:msgctl()

Eg:
/*meque_write.c*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>
#include<sys/ipc.h>
struct mymesg{
	long int mtype;
	char mtext[512];
};
int main()
{
	int id = 0;
	struct mymesg ckxmsg;
	key_t key = ftok("/tmp",66);//获取PIC键值
	id = msgget(key,IPC_CREAT | 0666);// IPC_CREAT不存在即创建
	if(id == -1)
	{
		printf("create msg error \n");
		return 0;
	}
	while(1)
	{
		char msg[512];
		memset(msg,0,sizeof(msg));
		ckxmsg.mtype = 1;//设置消息类型
		printf("input message:");
		fgets(msg,sizeof(msg),stdin);
		strcpy(ckxmsg.mtext,msg);//获取数据
 
		if(msgsnd(id,(void *)&ckxmsg,512,0) < 0)
		{
			printf("send msg error \n");
			return 0;
		}
 
		if(strncmp(msg,"QUIT",4) == 0)
			break;
	}
	if(msgctl(id,IPC_RMID,NULL) < 0)
	{
		printf("del msg error \n");
		return 0;
	}
	return 0;
}
/*meque_read.c*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/msg.h>
#include<sys/ipc.h>
struct mymesg{
	long int mtype;
	char mtext[512];
};
int main()
{
	int id = 0;
	struct mymesg ckxmsg;
	key_t key = ftok("/tmp",66);
	id = msgget(key,0666|IPC_CREAT);
	if(id == -1)
	{
		printf("open msg error \n");
		return 0;
	}
	while(1)
	{
		if(msgrcv(id,(void *)&ckxmsg,512,1,0) < 0)
		{
			printf("receive msg error \n");
			return 0;
		}
		printf("data:%s\n",ckxmsg.mtext);
		if(strncmp(ckxmsg.mtext,"QUIT",4) ==0)
			break;
	}
	return 0;
}
           

信号量

信号量是一种计数器,用来控制多个进程共享的资源进行的访问,常常被用作一个锁机制,在某个进程对特定资源进行操作时,信号量可以防止另一个进程去访问它。

1) 头文件:

#include<linux/sem.h>

#include<sys/types.h>

#include<sys/ipc.h>

/信号量数据结构体定义/

union semun

{

int val;

struct semid_ds *buf;

unsigned short *arry;

};

2) 函数:

a) 创建信号量:semget()

b) 信号量操作:semop():p/v操作 (P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行。V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1)

c) 控制信号量:semctl()

Eg:
1 #include <unistd.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 #include <fcntl.h>
  5 #include <stdlib.h>
  6 #include <stdio.h>
  7 #include <string.h>
  8 #include <sys/sem.h>
  9  /*信号量数据结构*/
 10 union semun
 11 {
 12         int val;
 13         struct semid_ds *buf;
 14         unsigned short *arry;
 15 };
 16 
 17 static int sem_id = 0;
 18 
 19 static int set_semvalue();
 20 static void del_semvalue();
 21 static int semaphore_p();
 22 static int semaphore_v();
 23 
 24 int main(int argc, char *argv[])
 25 {
 26         char message = 'X';
 27         int i = 0;
 28 
 29         //创建信号量
 30         sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
 31 
 32         if(argc > 1)
 33         {
 34                 //程序第一次被调用,初始化信号量
 35                 if(!set_semvalue())
 36                 {
 37                         fprintf(stderr, "Failed to initialize semaphore\n");
 38                         exit(EXIT_FAILURE);
 39                 }
 40                 //设置要输出到屏幕中的信息,即其参数的第一个字符
 41 
 42         message = argv[1][0];
 43 
 44                 //向屏幕中输出数据
 45                 printf("%c\n", message);
 46                 sleep(2);
 47         }
 48         for(i = 0; i < 4; ++i)
 49         {
 50                 //进入临界区
 51                 if(!semaphore_p())
 52                         exit(EXIT_FAILURE);
 53                 //向屏幕中输出数据
 54                 printf("%c", message);
 55                 //清理缓冲区,然后休眠随机时间
 56                 fflush(stdout);
 57                 sleep(rand() % 3);
 58                 //离开临界区前再一次向屏幕输出数据
 59                 printf("%c", message);
 60                 fflush(stdout);
 61                 //离开临界区,休眠随机时间后继续循环
 62                 if(!semaphore_v())
 63                         exit(EXIT_FAILURE);
 64                 sleep(rand() % 2);
 65         }
 66 
 67         sleep(10);
 68         printf("\n%d - finished\n", getpid());
 69 
 70         if(argc > 1)
 71         {
 72                 //如果程序是第一次被调用,则在退出前删除信号量
 73                 sleep(3);
 74                 del_semvalue();
 75         }
 76         exit(EXIT_SUCCESS);
 77 }
 78 
 79 static int set_semvalue()
 80 {
 81         //用于初始化信号量,在使用信号量前必须这样做
 82         union semun sem_union;
 83 
 84         sem_union.val = 1;
 85         if(semctl(sem_id, 0, SETVAL, sem_union) == -1)//setval 把单个信号量的值设置为联合体val的值
 86                 return 0;
 87         return 1;
 88 }
 89 
 90 static void del_semvalue()
 91 {
 92         //删除信号量
 93         union semun sem_union;
 94 
 95         if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
 96                 fprintf(stderr, "Failed to delete semaphore\n");
 97 }
 98 
 99 static int semaphore_p()
100 {
101         //对信号量做减1操作,即等待P(sv)
102         struct sembuf sem_b;//sembuf结构体
103         sem_b.sem_num = 0;
104         sem_b.sem_op = -1;//P()
105         sem_b.sem_flg = SEM_UNDO;
106         if(semop(sem_id, &sem_b, 1) == -1)
107         {
108                 fprintf(stderr, "semaphore_p failed\n");
109                 return 0;
110         }
111         return 1;
112 }
113 
114 static int semaphore_v()
115 {
116         //这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
117         struct sembuf sem_b;
118         sem_b.sem_num = 0;
119         sem_b.sem_op = +1;//V()
120         sem_b.sem_flg = SEM_UNDO;
121         if(semop(sem_id, &sem_b, 1) == -1)
122         {
123                 fprintf(stderr, "semaphore_v failed\n");
124                 return 0;
125         }
126         return 1;
127 }
           

无名管道:

(1) 无名管道主要用于有血缘关系的两个进程间通信,是内核使用环形队列机制实现,借助内核缓冲区实现的。

(2) 函数 pipe()

#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

int main(void)
{
    pid_t pid;
    char buf[1024];
    int fd[2];
    char *p = "test for pipe\n";
    
   if (pipe(fd) == -1) 
       sys_err("pipe");

   pid = fork();
   if (pid < 0) {
       sys_err("fork err");
   } else if (pid == 0) {
        close(fd[1]);//关闭写端
        int len = read(fd[0], buf, sizeof(buf));//读数据
        write(STDOUT_FILENO, buf, len);
        close(fd[0]);
   } else {
       close(fd[0]);//关闭读端
       write(fd[1], p, strlen(p));//写数据
       wait(NULL);
       close(fd[1]);
   }
    
    return 0;
}
           

有名管道

有名管道主要用于两个不相干的进程间通信,我认为之所以叫有名管道是因为他们借助mkfifo函数创建的伪文件利用内核缓冲区进行通信,因为创建文件可以指定文件名所以操作和使用文件几乎一样。

(1) 函数:

创建管道mkfifo

打开管道open

读管道read

写管道write

关闭管道close

删除管道unlink

/* read_fiofo.c*/
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/stat.h>
  4 #include <sys/types.h>
  5 #include <fcntl.h>
  6 #include <stdlib.h>
  7 #include <string.h>
  8 
  9 void sys_err(char *str)
 10 {
 11     perror(str);
 12     exit(1);
 13 }
 14 
 15 int main(int argc, char *argv[])
 16 {
 17     int fd, len;
 18     char buf[4096];
 19 
 20     if (argc < 2) {
 21         printf("./a.out fifoname\n");
 22         return -1;
 23     }
 24     fd = open(argv[1], O_RDONLY);
 25     if (fd < 0)
 26         sys_err("open");
 27     while (1) {
 28         len = read(fd, buf, sizeof(buf));
 29         write(STDOUT_FILENO, buf, len);
 30         sleep(3);           //多個读端时应增加睡眠秒数,放大效果.
 31     }
 32     close(fd);
 33 
 34     return 0;
 35 }
           
/* write_fiofo.c*/
1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/stat.h>
  4 #include <sys/types.h>
  5 #include <fcntl.h>
  6 #include <stdlib.h>
  7 #include <string.h>
  8 
  9 void sys_err(char *str)
 10 {
 11     perror(str);
 12     exit(-1);
 13 }
 14 
 15 int main(int argc, char *argv[])
 16 {
 17     int fd, i;
 18     char buf[4096];
 19 
 20     if (argc < 2) {
 21         printf("Enter like this: ./a.out fifoname\n");
 22         return -1;
 23     }
 24     fd = open(argv[1], O_WRONLY);
 25     if (fd < 0)
 26         sys_err("open");
 27 
 28     i = 0;
 29     while (1) {
 30         sprintf(buf, "hello itcast %d\n", i++);
 31 
 32         write(fd, buf, strlen(buf));
 33         sleep(1);
 34     }
 35     close(fd);
 36 
 37     return 0;
 38 }
 39 
           

socket

socket这里不做过多解释,内容比较多,大致流程下一章做个总结,下一章介绍下多线程,并发的一些网络编程

带你搞懂系统编程之--------进程进程