进程的创建有两种方式:一种是由操纵系统创建,一种是由父进程创建。在系统启动时,操作系统会创建一些进程,它们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程。系统允许一个进程创建新进程(即为子进程),子进程还可以创建新的子进程,形成进程树结构。整个Linux系统的所有进程也是一个树形结构。树根是系统自动构造的,即在内核态下执行的0号进程,它是所有进程的祖先。由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟存储管理的内核线程。随后,1号进程调用execve()运行可执行程序init(),并演变成用户态1号进程,即init进程。它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号等若干终端注册进程getty。每个getty进程设置其进程组标识号,并检测配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数execve()执行注册程序login,此时用户就可输入注册名和密码进入登陆过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。

进程组:是一个或多个进程的集合。进程组由进程组ID来唯一标识,除了进程号(PID)之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,组长进程的进程号等于进程组ID,且该进程组ID不会因组长进程的退出而受到影响。
会话期:是一个或多个进程组的集合。通常,一个会话开始与用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。

进程创建

Linux系统下使用fork()函数创建一个子进程,返回值是一个进程标识符(PID)。fork函数会创建一个新的进程,并从内核中为此进程分配一个新的可用的进程标识符,为这个新进程分配进程空间,并将父进程的进程空间中的内容复制到子进程的进程空间中,包括父进程的数据段和堆栈段,并且和父进程共享代码段。由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此,fork函数会返回两次,一次是在父进程中,一次是在子进程中。对于父进程,fork()函数返回新创建的子进程的ID。对于子进程,fork()函数返回0.如果创建出错,则fork()函数返回-1。

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

子进程继承资源的情况

资源 父子进程是否相同 资源 父子进程是否相同
进程ID 实际组ID
实际用户ID 有效组ID
有效用户ID 附加组ID
进程组ID .bss段
父进程ID 连接的共享存储段
会话ID 存储映射
设置用户ID标志 资源限制
设置组ID标志 tms_utime
当前工作目录 tms_stime
根目录 tms_cutime
文件权限屏蔽字 tms_ustime
信号的屏蔽 fork函数的返回值
打开的文件描述符 设置的文件锁
数据段 未处理的闹钟信号
代码段 未决信号集
堆栈段

fork函数复制当前进程,在内核进程表中创建一个新的进程表项。新的进程表项有很多属性和原进程相同,比如堆指针、栈指针和标志寄存器的值。但也有许多属性被赋予了新的值,比如该进程的PPID被设置成原进程的PID,信号位图被清除(原进程设置的信号处理函数不再对新进程起作用)。创建子进程后,父进程中打开的文件描述符默认在子进程中也是打开的,且文件描述符的引用计数加1。父进程的用户根目录、当前工作目录等变量的引用计数均会加1。

vfork

借尸还魂

在子进程中执行其他程序,即替换当前进程映像

#include <unistd.h>
extern char** environ;
int execl(const char* path, const char* arg, ...);
int execlp(const char* file, const char* arg, ...);
int execle(const char* path, const char* arg, ..., char* const envp[]);
int execv(const char* path, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execve(const char* path, char* const argv[], char* const envp[]);

path参数指定可执行文件的完整路径,file参数可以接受文件名,该文件的具体位置则在环境变量PATH中搜寻。arg接受可变参数,argv则接受参数数组,它们都会被传递给新程序的main函数。envp参数用于设置新程序的环境变量。如果未设置它,则新程序将使用由全局变量environ指定的环境变量。一般情况下,exec函数是不返回的,除非出错。它出错时返回-1,并设置errno。如果没出错,则原程序中exec调用之后的代码都不会执行,因此此时原程序已经被exec的参数指定的程序完全替换(包括代码和数据)。exec函数不会关闭原程序打开的文件描述符,除非该文件描述符被设置了类似SOCK_CLOEXEC的属性。

进程结束

Linux中进程退出分为正常退出和异常退出:

  • 正常退出 1.在main函数中执行return 2.调用exit函数 3.调用_exit函数
  • 异常退出 1.调用abort函数 2.进程收到某个信号,而该信号使程序终止

exit()函数的参数表示进程的退出状态,这个状态的值是一个整型,保存在全局变量$?中。

#include <stdlib.h>
void exit(int status);

$?是Linux shell中的一个内置变量,其中保存的是最近一次运行的进程的返回值。这个返回值有以下3种情况:1.程序中的main()函数运行结束,$?中保存main函数的返回值; 2.程序运行中调用exit函数结束运行,$?中保存exit函数的参数; 3.程序异常退出,$?中保存异常出错的错误号。可以通过shell执行echo $?来得到已结束进程结束状态。

exit和_exit函数

  1. 都是用来终止进程的,当程序执行到exit或_exit时,系统无条件地停止剩下所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。
  2. exit是在头文件stdlib.h中声明,而_exit声明在头文件unistd.h中声明。exit中的参数exit_code为0时,代表进程正常终止,若为其他值,表示程序执行过程中有错误发生。在调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin stdout stderr …)的数据。exit函数是_exit函数之上的一个封装,其会自动调用_exit,并在调用前先刷新流数据。
  3. exit函数与_exit函数最大区别就在于exit函数在调用_exit之前要检查文件打开情况,把文件缓存区的内容写回文件。

孤儿与僵尸进程

对于多进程程序,父进程一般需要跟踪子进程的退出状态。因此,当子进程结束运行时,内核不会立即释放该进程的进程表表项,以满足父进程后续对该子进程退出信息的查询(如果父进程还在运行)。
孤儿进程是指一个父进程退出后,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)收养,并由init进程对它们完成状态收集工作。
僵尸进程是指一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵尸进程。
孤儿进程是父进程已退出,而子进程未退出;僵尸进程是父进程未退出,而子进程已退出。

孤儿进程举例

  1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 int main()5 {6         pid_t pid;7         pid = fork();8         if(pid < 0)9         {10                 perror("fail to fork");11                 exit(-1);12         }else if(pid == 0){13                 //子进程14                 sleep(2);15                 printf("Sub-process: PID: %u, PPID: %u\n", getpid(), getppid());16         }else{17                 //父进程        18                 printf("Parent: PID: %u, Sub-process PID: %u\n", getpid(), pid);19         }20 }

僵尸进程举例

  1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 int main()5 {6         pid_t pid;7         pid = fork();8         if(pid < 0)9         {10                 perror("fail to fork");11                 exit(-1);12         }else if(pid == 0){13                 //子进程14                 printf("Sub-process: PID: %u, PPID: %u\n", getpid(), getppid());15                 exit(0); //子进程退出,成为僵尸进程16         }else{17                 //父进程        18                 printf("Parent: PID: %u, Sub-process PID: %u\n", getpid(), pid);19                 sleep(3);20                 printf("Parent is alive\n");21         }22 }


从图中可以看见僵尸进程,但等父进程睡眠醒来并退出,再查看会发现僵尸进程不见了。因为这个时候该进程变为了孤儿进程,过继给了init进程,而init进程会周期性地调用wait系统调用来清除各个僵尸的子进程。

进程一旦调用wait函数就阻塞自己,直到有信号来到或子进程结束。由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到已经变成僵尸的子进程,wait会收集这个子进程的信息,并把它彻底销毁后返回。子进程的结束状态值会由参数status返回,而子进程的进程码也会返回。如果不需要结束状态值,则参数status可设置成NULL。

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int* status, int options);

参数status指出了子进程是正常退出还是非正常结束,以及正常结束时的返回值或被哪个信号结束等信息。有一套专门的宏来提取这些信息。WIFEXITED(status)用来指出子进程是否正常退出,如果是,它会返回一个非零值。WEXITSTATUS(status),当子进程正常退出时,可以用这个宏提取子进程的返回值。如果进程异常退出,返回0,这个值没有意义。

  1 #include <sys/types.h>2 #include <sys/wait.h>3 #include <stdio.h>4 #include <stdlib.h>5 #include <unistd.h>6 int main()7 {8         pid_t pid = fork();9         if(pid < 0)10         {11                 perror("fail to fork");12                 exit(-1);13         }else if(pid == 0){14                 //子进程15                 printf("Sub-process: PID: %u, PPID: %u\n", getpid(), getppid());16                 exit(3);17         }else{18                 //父进程        19                 printf("Parent: PID: %u, Sub-process PID: %u\n", getpid(), pid);20                 int status = -1;21                 pid_t pr = wait(&status);22                 if(WIFEXITED(status)){23                         printf("the child process %d exit normally.\n",pr);24                         printf("the return code is %d.\n",WEXITSTATUS(status));25                 }else{26                         printf("the child process %d exit abnormally.\n",pr);27                 }28         }29         return 0;30 }


waitpid是wait的封装,多出了可由用户控制的参数pid和options。
参数pid为欲等待的子进程识别码

  • pid < -1:等待进程组识别码为pid绝对值的任何子进程

  • pid = -1:等待任何子进程,相当于wait

  • pid = 0:等待进程组识别码与目前进程相同的任何子进程

  • pid = 0:等待任何子进程识别码为pid的子进程
    参数options的值有以下几种类型

  • options=WNOHANG 即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去

  • options=WUNTRACED 子进程进入暂停则马上返回,但结束状态不予理会
    waitpid返回值

  • 当正常返回的时候waitpid返回收集的子进程的进程ID

  • 如果设置了WNOHANG,而调用waitpid发现没有已退出的子进程可收集,则返回0

  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在

  • 当pid所指示的子进程不存在,或此进程存在,当不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD

  1 #include <sys/types.h>2 #include <sys/wait.h>3 #include <stdio.h>4 #include <stdlib.h>5 #include <unistd.h>6 int main()7 {8         pid_t pid = fork();9         if(pid < 0)10         {11                 perror("fail to fork");12                 exit(-1);13         }else if(pid == 0){14                 //子进程15                 printf("Sub-process: PID: %u, PPID: %u\n", getpid(), getppid());16                 sleep(10);17                 exit(0);18         }else{19                 //父进程        20                 printf("Parent: PID: %u, Sub-process PID: %u\n", getpid(), pid);21                 pid_t pr;22                 do{23                         pr = waitpid(pid,NULL,WNOHANG);24                         if(pr==0)25                         {26                                 printf("No child exited\n");27                                 sleep(1);28                         }29                 }while(pr==0);30                 if(pr==pid){31                         printf("successfully get the child process %d\n",pr);32                 }else{33                         printf("some error occured\n");34                 }35         }36         return 0;37 }

守护进程

Linux系统中,每个系统与用户进行交互的界面称为终端,每个从此终端开始运行的进程都回依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端关闭时,相应的进程都会自动关闭。守护进程能脱离终端并在后台运行,守护进程脱离终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。

  1. 创建子进程,父进程退出 子进程由1号进程收养
  2. 在子线程中创建新会话,使用系统函数setsid用于创建一个新的会话,并担任会话组的组长。调用该函数有3个作用:让进程摆脱原会话的控制,让进程摆脱原进程组的控制,让进程摆脱原控制终端的控制。由于在调用fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,setsid函数能够使进程完全独立出来。
  3. 改变当前目录为根目录
  4. 重设文件权限掩码
  5. 关闭文件描述符
    在第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能到达守护进程,守护进程中用常规方法输出的字符也不可能中终端上显示出来。所以,文件描述符0、1、2已经失去存在的价值,需要关闭这些文件描述符。

实现一个守护进程,每隔10s在/tmp/dameon.log中写入一句话

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MAXFILE 65535
int main()
{pid_t pc;int i,fd,len;char* buf = "this is a Dameon\n";len = strlen(buf);pc = fork(); //第一步if(pc<0){printf("error fork\n");exit(1);}else if(pc>0){exit(0);}setsid(); //第二步chdir("/"); //第三步umask(0); //第四步for(i=0;i<MAXFILE;i++) //第五步{close(i);}while(1){if((fd=open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0){perror("open error");exit(1);}write(fd,buf,len+1);close(fd);sleep(10);}return 0;
}

信号

信号是UNIX和Linux系统响应某些条件而产生的事件。接收到该信号的进程会相应地采取一些行动。可以由shell和终端处理器生成中断,也可以作为在进程间传递消息或修改行为的一种方式,明确地由一个进程发送给另一个进程。

进程间通信

进程间通信(Inter Process Communication, IPC):管道、消息队列、共享内存、信号量、套接字等。最初由AT&T System V2版本的UNIX引入的是消息队列、共享内存、信号量。

管道

管道是一种两个进程间进行单向通信的机制(半双工)。数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道),如果要进行双工通信,则需要建立两个管道。管道的缓冲区大小是受限制的,所传输的是无格式的字节流等。通过管道通信的两个进程,一个进程向管道写数据,另一个从中读数据。写入的数据每次都添加到管道缓冲区的末尾,读数据的时候都是从缓冲区的头部读出数据。

无名管道 有名管道
管道只能用于父子进程或兄弟进程间通信,也就是说管道只能用于具有亲缘关系的进程间通信。 可以使互不相关的进程实现通信
存在于内核中,不可见 可以通过路径名指出,在文件系统中可见

无名管道

#include <unistd.h>
int pipe(int fd[2]);

描述字fd[0]称为管道读端,描述字fd[1]称为管道写端。一般的文件I/O函数都可以用于管道,如close、read、write等。

  1 #include <unistd.h>2 #include <stdio.h>3 #include <stdlib.h>4 #include <string.h>5 #define INPUT 06 #define OUTPUT 17 int main()8 {9         int fd[2];10         pid_t pid;11         char buf[256];12         //创建无名管道13         pipe(fd);14         pid = fork();15         if(pid<0)16         {17                 printf("Error in fork\n");18                 exit(1);19         }else if(pid == 0){20                 printf("in the child process...\n");21                 //子进程向父进程写数据,关闭管道的读端22                 close(fd[INPUT]);23                 write(fd[OUTPUT],"hello world", strlen("hello world"));24                 exit(0);25         }else{26                 printf("in the parent process...\n");27                 //父进程从管道读取子进程写的数据,关闭管道写端28                 close(fd[OUTPUT]);29                 int returned_count = read(fd[INPUT],buf,sizeof(buf));30                 printf("%d bytes of data received from child process: %s\n", returned_count,bu    f);31         }32         return 0;33 }

有名管道(named pipe或FIFO)

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname, mode_t mode);

该函数的第一个参数是一个普通的路径名,也就是创建FIFO的名字。第二个参数与打开普通文件的open()函数中的mode参数相同。如果mkfifo的第一个参数是一个已存在的路径名时,会返回EEXIST错误。
mkfifo_r.cpp

  1 #include <stdio.h>2 #include <sys/stat.h>3 #include <fcntl.h>4 #include <unistd.h>5 #include <string.h>6 #include <stdlib.h>7 #define P_FIFO "/tmp/p_fifo"8 int main()9 {10         char cache[100];11         int fd;12         memset(cache,0,sizeof(cache));13         if(access(P_FIFO,F_OK)==0) //管道文件存在14         {15                 execlp("rm","-f",P_FIFO,NULL);16                 printf("remove FIFP file\n");17         }18         if(mkfifo(P_FIFO,0777)<0)19         {20                 printf("createnamed pipe failed\n");21                 return 0;22         }23         fd = open(P_FIFO,O_RDONLY|O_NONBLOCK); //非阻塞方式打开,只读24         while(1)25         {26                 memset(cache,0,sizeof(cache));27                 if(read(fd,cache,100)==0)28                 {29                         printf("nodata\n");30                 }else{31                         printf("getdata: %s\n",cache); //读到数据32                 }33                 sleep(1);34         }35         close(fd);36         return 0;37 }

mkfifo_w.cpp

  1 #include <stdio.h>2 #include <fcntl.h>3 #include <unistd.h>4 #define P_FIFO "/tmp/p_fifo"5 int main(int argc,char **argv)6 {7         int fd;8         if(argc < 2)9                 printf("please input the write data/\n");10         fd = open(P_FIFO,O_WRONLY|O_NONBLOCK);11         write(fd,argv[1],100);12         close(fd);13         return 0;14 }

消息队列

消息队列用于运行于同一台机器上的进程间通信,每个数据块都有一个特定的类型,接收方可以根据类型来有选择地接收数据,而不一定像管道和命名管道那样必须以先进先出的方式接收数据。消息队列是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现。消息链表中节点的结构用msg声明。

创建新消息队列或取得已存在消息队列

int msgget(ket_t key, int msgflg);

参数中key可以认为是一个端口号,也可以由函数ftok生成,msgflg如果等于IPC_CREAT,若没有该队列,则创建一个并返回新标识符,若已存在则返回原标识符;msgflg如果等于IPC_EXCL,若没有该队列,则返回-1,若已存在,则返回0。
向队列读/写消息

ssize_t msgrcv(int msqid, void* msgp, size_t msgsz, long msgtyp, int msgflg);
int msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg);

参数中msqid是消息队列的识别码,msgp是指向消息缓冲区的指针。此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构。一般定义为如下结构:

 struct msgstru{long msg_type;char mtext[512];};

msgsz是指消息的数据部分mtext的大小;msgtyp是指从消息队列内读取的消息形态。msgflg用来指明核心程序在队列没有数据的情况下所应采取的行动。如果msgflg和常数IPC_NOWAIT合用,则在msgsnd执行时若是消息队列已满,则msgsnd将不会阻塞,而会立即返回-1,如果执行的是msgrcv,则在消息队列呈空时,立刻返回-1,并设定错误码为ENOMSG。当msgflg为0时,msgsnd及msgrcv在队列呈满或呈空的情况时,采用阻塞等待的处理模式。

设置消息队列属性

int msgctl(int msgqid, int cmd, struct msqid_ds *buf);

参数中的msgctl系统调用对msgqid标识的消息队列执行cmd操作,系统定义了3种cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。IPC_STAT用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间;IPC_SET用来设置消息队列的属性,要设置的属性存储在buf中;IPC_RMID用来从内核中删除msqid标识的消息队列。

  1 #include <unistd.h>2 #include <stdlib.h>3 #include <stdio.h>4 #include <string.h>5 #include <errno.h>6 #include <sys/msg.h>7 #define MAX_TEXT 5128 struct msg_st{9         long int msg_type;10         char text[MAX_TEXT];11 };12 int main()13 {14         int running = 1;15         int msgid = -1;16         struct msg_st data;17 18         //建立消息队列19         msgid = msgget((key_t)1234,0666|IPC_CREAT);20         if(msgid == -1)21         {22                 fprintf(stderr,"msgget failed with error: %d\n",errno);23                 exit(EXIT_FAILURE);24         }25         //从队列中获取消息,直到遇到end消息26         while(running)27         {28                 if(msgrcv(msgid,(void*)&data,BUFSIZ,0,0)==-1)29                 {30                         fprintf(stderr,"msgrcv failed with errno: %d\n", errno);31                         exit(EXIT_FAILURE);32                 }33                 printf("You wrote: %s\n",data.text);34                 if(strncmp(data.text,"end",3) == 0)35                 {36                         running = 0;37                 }38         }39         //删除消息队列40         if(msgctl(msgid,IPC_RMID,0)==-1)41         {42 43                 fprintf(stderr,"msgctl failed");44                 exit(EXIT_FAILURE);45         }46         exit(EXIT_SUCCESS);47 }
  1 #include <unistd.h>2 #include <stdlib.h>3 #include <stdio.h>4 #include <string.h>5 #include <errno.h>6 #include <sys/msg.h>7 #define MAX_TEXT 5128 struct msg_st{9         long int msg_type;10         char text[MAX_TEXT];11 };12 int main()13 {14         int running = 1;15         int msgid = -1;16         struct msg_st data;17         char buffer[MAX_TEXT];18         //建立消息队列19         msgid = msgget((key_t)1234,0666|IPC_CREAT);20         if(msgid == -1)21         {22                 fprintf(stderr,"msgget failed with error: %d\n",errno);23                 exit(EXIT_FAILURE);24         }25         //向队列中写入消息,直到遇到end消息26         while(running)27         {28                 printf("Enter some text: ");29                 fgets(buffer, MAX_TEXT, stdin);30                 data.msg_type = 1;31                 strcpy(data.text,buffer);32                 if(msgsnd(msgid,(void*)&data,MAX_TEXT,0)==-1)33                 {34                         fprintf(stderr,"msgsnd failed\n");35                         exit(EXIT_FAILURE);36                 }37                 if(strncmp(data.text,"end",3) == 0)38                 {39                         running = 0;40                 }41                 sleep(1);42         }43         exit(EXIT_SUCCESS);44 }


一种正逐渐被淘汰的通信方式,完全可以用流管道或者套接口的方式来取代它。

共享内存

共享内存就是允许两个不相关的进程访问同一个逻辑内存。不同进程之间共享的内存通常安排在同一物理内存中。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。不过,共享内存并未提供同步机制,也就是,在第一个进程对共享内存的写操作结束之前,并无自动机制可以阻止第二个进程对它进行读取。

创建共享内存

#include <sys/shm.h>
int shmget(key_t key, int size, int flag);

第一个参数程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget函数运行成功时会返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数;调用失败返回-1。第二个参数,size以字节为单位指定需要共享的内存容量。第三个参数是权限标志,作用和open函数的mode参数一样。
将共享内存链接到自身的地址空间

void *shmat(int shmid, void* addr, int flag);

shmid为shmget函数返回的共享存储标识符。函数的返回值即是该进程数据段所链接的实际地址。

将共享内存从当前进程中分离

int shmdt(const void* shmaddr);

将共享内存分离并不是删除它,而是使共享内存对当前进程不可用。

sharememconsumer.cpp

  1 #include <unistd.h>2 #include <stdlib.h>3 #include <stdio.h>4 #include <string.h>5 #include <sys/shm.h>6 #define TEXT_SZ 20487 struct shared_use_st8 {9         int written;10         char text[TEXT_SZ];11 };12 int main()13 {14         int shmid;15         shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);16         if(shmid==-1)17         {18                 fprintf(stderr,"shmget failed\n");19                 exit(EXIT_FAILURE);20         }21         void* shared_memory = (void*)0;22         shared_memory = shmat(shmid,(void*)0,0);23         if(shared_memory==(void*)-1)24         {25                 fprintf(stderr,"shmat failed\n");26                 exit(EXIT_FAILURE);27         }28         printf("Memory attached at %X\n",(long)shared_memory);29         struct shared_use_st *shared_stuff;30         shared_stuff = (struct shared_use_st*)shared_memory;31         shared_stuff->written = 0;32         int running = 1;33         while(running)34         {35                 if(shared_stuff->written)36                 {37                         printf("You wrote: %s", shared_stuff->text);38                         sleep(1);39                         shared_stuff->written = 0;40                         if(strncmp(shared_stuff->text,"end",3)==0)41                         {42                                 running = 0;43                         }44                 }45         }46         if(shmdt(shared_memory)==-1)47         {48                 fprintf(stderr,"shmdt failed\n");49                 exit(EXIT_FAILURE);50         }51         if(shmctl(shmid,IPC_RMID,0)==-1)52         {53                 fprintf(stderr,"shmctl failed\n");54                 exit(EXIT_FAILURE);55         }56         exit(EXIT_SUCCESS);57 }

sharememproducer.cpp

  1 #include <unistd.h>2 #include <stdlib.h>3 #include <stdio.h>4 #include <string.h>5 #include <sys/shm.h>6 #define TEXT_SZ 20487 struct shared_use_st8 {9         int written;10         char text[TEXT_SZ];11 };12 int main()13 {14         int shmid;15         shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);16         if(shmid==-1)17         {18                 fprintf(stderr,"shmget failed\n");19                 exit(EXIT_FAILURE);20         }21         void* shared_memory = (void*)0;22         shared_memory = shmat(shmid,(void*)0,0);23         if(shared_memory==(void*)-1)24         {25                 fprintf(stderr,"shmat failed\n");26                 exit(EXIT_FAILURE);27         }28         printf("Memory attached at %X\n",(long)shared_memory);29         struct shared_use_st *shared_stuff;30         shared_stuff = (struct shared_use_st*)shared_memory;31         shared_stuff->written = 0;32         int running = 1;33         char buffer[BUFSIZ];34         while(running)35         {36                 while(shared_stuff->written == 1)37                 {38                         sleep(1);39                         printf("waiting for client...\n");40                 }41                 printf("Enter some text: ");42                 fgets(buffer,BUFSIZ,stdin);43                 strncpy(shared_stuff->text,buffer,TEXT_SZ);44                 shared_stuff->written = 1;45                 if(strncmp(buffer,"end",3)==0)46                 {47                         running = 0;48                 }49 50         }51         if(shmdt(shared_memory)==-1)52         {53                 fprintf(stderr,"shmdt failed\n");54                 exit(EXIT_FAILURE);55         }56         exit(EXIT_SUCCESS);57 }


信号量

当多个进程同时访问系统上的某个资源的时候,比如同时写一个数据库的某条记录,或者同时修改某个文件,就需要考虑进程的同步问题,以确保任一时刻只有一个进程可以拥有对资源的独占式访问。信号量是一种特殊的变量,它只能取自然数值并且只支持两种操作:等待(wait)和信号(signal)。即P(passeren 传递,就像进入临界区)和V(vrijgeven 释放 就像退出临界区)操作。
假设有信号量SV,则对它的P、V操作:

  • P(SV),如果SV的值大于0,就将它减1;如果SV的值为0,则挂起进程的执行
  • V(SV),如果有其他进程因为等待SV而挂起,则唤醒之;如果没有,则将SV加1
    信号量的取值可以是任何自然数。但最常用的、最简单的信号量是二进制信号量,它只能取0和1这两个值。

    当关键代码段可用时,二进制信号量SV的值为1,进程A和B都有机会进入关键代码段。如果此时进程A执行P(SV)操作将SV减1,则进程B若再执行P(SV)操作就会被挂起。直到进程A离开关键代码段,并执行V(SV)操作将SV加1,关键代码段才重新变得可用。如果此时进程B因为等待SV而处于挂起状态,则它将被唤醒。

共享内存是进程间通信的最快的方式,但是共享内存的同步问题自身无法解决(即进程该何时去共享内存取得数据,而何时不能取),但用SYSTEM V信号量可轻易解决这个问题(使用信号量解决共享内存的同步问题)。

创建和打开信号量集

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

该函数执行成功返回信号量标识符,失败则返回-1。参数key是函数通过调用ftok函数得到的键值,用来标识一个全局唯一的信号量集,nsems代表创建信号量的个数,如果只是访问而不创建则可以指定该参数为0;但一旦创建了该信号量,就不能更改其信号量个数。只要不删除该信号量,就可以重新调用该函数创建该键值的信号量,该函数只是返回以前创建的值,而不会重新创建。semflg指定该信号量的读写权限,它低端的9个比特是该信号量的权限,其格式和含义都与系统调用open的mode参数相同。

改变信号量的值,即执行P、V操作

int semop(int semid, struct sembuf *sops, unsigned nsops);

semid是由semget返回的信号量标识符,sembuf结构的定义如下:

struct sembuf{unsigned short int sem_num; short int sem_op;     //信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,一个是+1,即V(发送信号)操作short int sem_flg;    //通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,操作系统释放信号量
};

其中,sem_num成员是信号量集中信号量的编号,0表示信号量集中的第一个信号量。sem_op成员指定操作类型,其可选值为正整数、0和负整数。每种类型的操作的行为受到sem_flg成员的影响。

直接控制信号量信息

int semctl(int semid, int semnum, int cmd, ...);

cmd通常是SETVAL或IPC_RMID。SETVAL用来把信号量初始化为一个已知的值,值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。IPC_RMID用于删除一个已经无须继续使用的信号量标识符。如果有第4个参数,它通常是一个union semum结构,定义如下:

union semun{int val;struct semid_ds *buf;unsigned short *arry;
};

semreader.cpp

  1 #include <sys/types.h>2 #include <sys/ipc.h>3 #include <sys/sem.h>4 #include <stdio.h>5 #include <unistd.h>6 #include <stdlib.h>7 #include <string.h>8 #include <sys/shm.h>9 #include <errno.h>10 #define SEM_KEY 400111 #define SHM_KEY 567812 union semun{13         int val;14 };15 int main(void)16 {17         int semid,shmid;18         //创建共享内存19         shmid = shmget(SHM_KEY,sizeof(int),IPC_CREAT|0666);20         if(shmid < 0)21         {22                 printf("create shm error\n");23                 return -1;24         }25         void* shmptr = (void*)0;26         shmptr = shmat(shmid,NULL,0); //连接到自身空间中27         if(shmptr == (void*)-1)28         {29                 printf("shmat error: %s\n",strerror(errno));30                 return -1;31         }32         int* data = (int*)shmptr;33         //创建一个semid,有两个信号量34         semid = semget(SEM_KEY,2,IPC_CREAT|0666);35         union semun semun1;36         semun1.val = 0;37         semctl(semid,0,SETVAL,semun1);38         semun1.val = 1;39         semctl(semid,1,SETVAL,semun1);40         struct sembuf sembuf1;41         while(1)42         {43                 sembuf1.sem_num = 0; //sem_num指的是下面操作指向第一个信号量44                 sembuf1.sem_op = -1; //设置为-1为等待45                 sembuf1.sem_flg = SEM_UNDO;46                 semop(semid,&sembuf1,1); //阻塞,直到收到信号47                 printf("the num: %d\n",*data);48                 sembuf1.sem_num = 1; //sem_num指的是下面操作指向第二个信号量49                 sembuf1.sem_op = 1; //设置为1为发送信号50                 sembuf1.sem_flg = SEM_UNDO;51                 semop(semid,&sembuf1,1);52         }53         return 0;54 }

semwriter.cpp

  1 #include <sys/types.h>2 #include <sys/ipc.h>3 #include <sys/sem.h>4 #include <stdio.h>5 #include <unistd.h>6 #include <stdlib.h>7 #include <string.h>8 #include <sys/shm.h>9 #include <errno.h>10 #define SEM_KEY 400111 #define SHM_KEY 567812 union semun{13         int val;14 };15 int main(void)16 {17         int semid,shmid;18         //创建共享内存19         shmid = shmget(SHM_KEY,sizeof(int),IPC_CREAT|0666);20         if(shmid < 0)21         {22                 printf("create shm error\n");23                 return -1;24         }25         void* shmptr = (void*)0;26         shmptr = shmat(shmid,NULL,0); //连接到自身空间中27         if(shmptr == (void*)-1)28         {29                 printf("shmat error: %s\n",strerror(errno));30                 return -1;31         }32         int* data = (int*)shmptr;33         //获得一个semid,有两个信号量34         semid = semget(SEM_KEY,2,0666);35         union semun semun1;36         struct sembuf sembuf1;37         while(1)38         {39                 sembuf1.sem_num = 1; //sem_num指的是下面操作指向第二个信号量40                 sembuf1.sem_op = -1; //设置为-1为等待,因为第2个信号量初始值为1,所以下面不会>    阻塞41                 sembuf1.sem_flg = SEM_UNDO;42                 semop(semid,&sembuf1,1);43                 scanf("%d",data);44                 sembuf1.sem_num = 0; //sem_num指的是下面操作指向第一个信号量45                 sembuf1.sem_op = 1; //设置为1为发送信号46                 sembuf1.sem_flg = SEM_UNDO;47                 semop(semid,&sembuf1,1);48                 //执行加1后,reader阻塞是由于第一个信号量为0,无法减1,而现在加1后,reader就绪后writer继续循环,发现第二个信号量已经减为0,则阻塞了49         }50         return 0;51 }


dbus

上面三种System V IPC进程间通信方式都使用一个全局唯一的键值来描述一个共享资源。当程序调用semget、shmget或msgget时,就创建了这些共享资源的一个实例。Linux提供了ipcs命令,以观察当前系统上拥有哪些共享资源实例。可以使用ipcrm命令删除遗留在系统中的共享资源。
ipcs -a 用于列出本用户所有相关的ipcs参数
ipcs -q 用于列出进程中的消息队列
ipcs -s 用于列出所有信号量
ipcs -m 用于列出所有的共享内存信息
ipcs -l 用于列出系统限额
ipcs -t 用于列出最后的访问时间
ipcs -u 用于列出当前使用情况

Linux编程入门四进程相关推荐

  1. linux编程取消wait函数,Linux编程基础之进程等待(wait()函数).pdf

    Linux编程基础之进程等待(wait()函数) 编程过程中,有时需要让一个进程等待另一个进程 ,最常见的是父进程等待自己的子进程 ,或者父进程回收自己 的子进程资源包括僵尸进程.这里简单介绍一下系统 ...

  2. Linux编程入门(2)-实现who指令

    上一篇简单介绍了Linux系统编程的一些概念知识,从本篇文章开始,从解释系统命令的功能入手,由浅入深,逐步讲解Linux系统编程. 建议学习者最好具有一定的C语言基础,了解数组.结构体.指针和链表的概 ...

  3. Linux编程(10)_进程通信

    1 进程通信相关概念 1 什么是IPC 进程间通信, InterProcess Communication 2 进程间通信常用几种方式 管道 (使用最简单) 信号 (开销最小) 共享内存/映射区 (无 ...

  4. linux ps-e和-ax区别,Linux编程 6 (查看进程 ps 及输出风格)

    一.查看进程命令ps 1.1 默认ps 命令 在默认情况下,ps命令只会显示运行在当前控制台下,属于当前用户的进程,在上图中,我们只运行了bash shell以及ps命令本身. 上图中显示了程序的进程 ...

  5. 【Linux编程】守护进程(daemon)详解与创建

    本文主要参考自:linux系统编程之进程(八):守护进程详解及创建,daemon()使用 一.概述 Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处 ...

  6. linux查看运行的程序c pu,Linux系统入门之进程及任务管理命令

    Linux系统上所有运行的东西都可以称之为一个进程.每个用户任务.每个系统管理守护进程都可以称之为进程.Linux用分时管理方法使所有的任务共同分享系统资源.以下将介绍一些常用的查看和控制进程的命令. ...

  7. linux编程两个子进程,Linux中fork同时创建多个子进程的方法

    怎么创建多个进程呢?我说那还不容易,看下边代码: //省略必要头文件 int main() { pid_t pid[2]; int i; printf("This is %d\n" ...

  8. Linux编程(9)_进程

    1 进程相关概念 1 程序和进程 程序: 二进制文件, 占用的磁盘空间 进程: 启动的程序, 所有数据都在内存, 不仅占用内存空间, 也需要占用更多的系统资源, 例如CPU, 物理内存 2 并行和并发 ...

  9. Unix/Linux编程:四种mbuf

    网络协议对内核的存储器管理能力提出了很多要求.这些要求包括能方便的可变长缓存,能在缓存头部和尾部添加数据(封装时需要添加首部),能从缓存中移除数据(解封装时要移除首部),并能尽量减少为这些操作所做的数 ...

最新文章

  1. java io 文件复制_实例讲述Java IO文件复制
  2. Django + Nginx + Uwsgi + Celery + Rabbitmq 做一个高速响的应网站架构
  3. spring mvc+spring + hibernate 整合(二)
  4. Android Annotation注解详解
  5. 771. Jewels and Stones 宝石与石头
  6. 数组concat_js 标准二维数组变一维数组的方法
  7. mysql 编码php,php-MySQL的编码问题(和基础知识)
  8. Python实现用户登录
  9. ML之FE:利用【数据分析+数据处理】算法对国内某平台上海2020年6月份房价数据集【12+1】进行特征工程处理(史上最完整,建议收藏)——附录
  10. 如何用计算机看苹果手机的文件,如何在电脑上管理iphone文件?
  11. 简单,充实,幸福——2015-2016年终总结
  12. 十大排序方法之基数排序
  13. 计算天数c语言实验报告,c语言实例--计算天数
  14. 【森气杂谈】群晖NAS内外网磁盘映射以及quick connect设置
  15. python爬虫之自动填写问卷星调查表
  16. 迎接互联网的辛迪加时代
  17. Tushare介绍、安装及使用教程
  18. 错失项目汇报机会是一种罪过
  19. matlab仿真电气连接,MATLAB仿真及在电子信息与电气工程中的应用简介,目录书摘...
  20. 全网最全的用手机给电脑装系统,不服来战!

热门文章

  1. 聊聊互联网行业对35岁码农的偏见,以及大龄码农的破局之道
  2. SQL Server数据库中创建数据表及数据类型操作应用
  3. Android手机连接到Tomcat服务器
  4. Flutter学习-多子布局Widget
  5. 人脸识别+深度学习,水平远超人类大脑!
  6. 量子计算机的内存有多大,人类大脑的容量有多大,相当于多少G的内存?答案你都不敢相信...
  7. BIM特点及格式文件说明
  8. 苹果xr十大隐藏功能_苹果iPhone12Pro隐藏功能!简单3步骤“量身高”
  9. cboard企业版源码_CBoard自助BI数据分析产品 v0.4.2
  10. 所有的 Boost 库文档的索引