【注册信号处理函数,不推荐使用】
函数原型: void (*signal (int signo, void (*func)(int))) (int); 参数说明: signo:表示准备捕获或忽略的信号值 func:处理该信号应调用的函数,也可以用如下两个特殊值来代替 SIG_IGN:忽略信号 SIG_DFL:恢复默认行为函数名是signal,返回值是void (*XXXX) (int)。XXXX为一个函数指针。函数signal的参数中,func是指向的一个函数的指针。
typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
函数示例:
#include <signal.h> #include <stdio.h> #include <unistd.h> void ouch(int sig) { printf("OUCH! -I got signal %d\n", sig); void signal(SIGINT, SIG_DFL); } int main() { (void) signal(SIGINT, ouch); while(1) { printf("Hello World!\n"); sleep(1); } }第一次按Ctrl+c让程序作出响应,然后继续执行。再按一次Ctrl+c程序结束,因为SIGINT信号的处理方式已恢复成默认。
【注册信号处理函数,推荐使用】
头文件 #include<signal.h> 函数原型: int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact); 结构sigaction定义如下: struct sigaction{ void (*sa_handler)(int); /* function, SIG_DFL or SIG_IGN */ sigset_t sa_mask; /* signals to block in sa_handler */ int sa_flag; /* signal action modifiers */ void (*sa_sigaction)(int,siginfo_t *,void *); }; 参数说明: signo:处理的信号 act:该结构的作用是定义在接收到信号后应该采取的行动。 oact:一般设为空,非空则将原来的行动返回出来。 sa_handler:字段包含一个信号处理函数 sa_mask:字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加进进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。头文件 signal.h 中有一组函数用来操作信号集sigset_t,它们分别是 sigaddset 、 sigemptyset 、 sigfillset 和 sigdelset 等。 sa_flag:是一个选项,主要理解两个 SA_INTERRUPT 由此信号中断的系统调用不会自动重启 SA_RESTART 由此信号中断的系统调用会自动重启 SA_SIGINFO 提供附加信息,一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针 SA_RESETHAND 当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL SA_NODEFER 捕获到信号时不将它添加到信号屏蔽字中 SA_NOCLDSTOP 子进程停止时不产生SIGCHLD信号
创建信号集sigset_t函数有如下5个:
1,sigemptyset:初始化信号集合为空。
2,sigfillset:把所有信号加入到集合中,信号集中将包含Linux支持的64种信号。,
3,sigaddset:将指定信号加入到信号集合中去。,
4,sigdelset:将指定信号从信号集中删去。
5,sigismember:查询指定信号是否在信号集合之中。
用 sigaction 来截获 SIGINT 信号:
#include <stdio.h> #include <unistd.h> #include <signal.h> void ouch(int sig){ printf("OUCH! - I got signal %d/n", sig); } int main() { struct sigaction act; act.sa_handler = ouch; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT, &act, 0); while(1){ printf("hello. /n"); sleep(1); } return 0; }按下Ctrl+C组合键,就可以看到一条消息。因为sigaction函数连续处理到来的SIGINT信号。要想终止这个程序,按下Ctrl+\组合键,它默认情况下产生SIGQUIT信号。
函数原型: int kill(pid_t pid, int sig); 参数说明: pid:可能选择有以下四种 1. pid>0,pid是信号欲送往的进程的标识。 2. pid=0,信号将送往所有与调用kill()的那个进程属同一个使用组的进程。 3. pid=-1,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。 4. pid<-1,信号将送往以-pid为组标识的进程。 int:返回值,成功执行时,返回0。失败返回-1
头文件: #include<unistd.h> 定义函数: int pause(void); 函数说明: pause()会令当前的进程暂停(进入睡眠状态),直到被信号(signal)所中断。 返回值: 被一个信号中断时只返回-1。 错误代码 EINTR 有信号到达中断了此函数。
此标准的库函数通过在系统内部调用pipe()来创建一个半双工的管道,然后它创建一个子进程,启动shell,最后在shell上执行command参数中的命令。管道中数据流的方向是由第二个参数type控制的。此参数可以是r或者w,分别代表读或写。但不能同时为读和写。
函数原型: FILE *popen(char*command,char*type); 参数说明: FILE*:返回值,如果成功,返回一个新的文件流。如果无法创建进程或者管道,返回NULL。
函数原型: int pclose(FILE*stream); 参数说明: int:返回值,返回系统调用wait4()的状态。-1表示错误。事实上,popen会fork一个进程,而pclose会wait该子进程并关闭。
【示例代码】:用popen传输大数据:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main() { FILE *read_fp; char buffer[BUFSIZ+1]; int chars_read; memset(buffer,'\0',sizeof(buffer)); read_fp = popen("ps -ax","r"); if(read_fp != NULL) { chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp); while(chars_read > 0) { buffer[chars_read] = '\0'; printf("Reading:-\n %s\n",buffer); chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp); } pclose(read_fp); exit(EXIT_SUCCESS); } exit(EXIT_FAILURE); }
函数原型: int pipe(int file_descriptor[2]); 参数说明: file_descriptor:传出参数,该参数将被函数调用后填写。任何写入file_descriptor[1]的数据可以由file_descriptor[0]中读取。 int:返回值,-1表示失败。
【示例程序】单独创建了一个管道(pipe)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; char buffer[BUFSIZ +1]; memset(buffer,'\0',sizeof(buffer)); if(pipe(file_pipes) == 0) { data_processed = write(file_pipes[1],some_data,strlen(some_data)); printf("Wrote %d bytes\n",data_processed); data_processed = read(file_pipes[0],buffer,BUFSIZ); printf("Read %d bytes: %s\n",data_processed,buffer); exit(EXIT_SUCCESS); } exit(EXIT_FAILURE); }这个程序使用两个文件描述符file_pipes[]来创建一个管道。然后他使用文件描述符file_pipes[1]向管道中写入数据,并且由file_pipes[0]中读取。注意管道有内部缓冲,从而可以在两个调用write与read之间存储数据。
dup和dup2也是两个非常有用的调用,它们的作用都是用来复制一个文件的描述符。它们经常用来重定向进程的stdin、stdout和stderr。
函数原型: int dup(int file_descriptor); 参数说明: file_descriptor:传入的已有文件描述符,该函数创建一个新的文件描述符(作为已有的拷贝)
函数原型: int dup2(int file_descriptor_one , int file_descriptor_two); 参数说明: file_descriptor_one:传入的已有文件描述符,dup2它所创建的新文件描述符或者与参数file_descriptor_two相同,或者是第一个大于该参数的可用值。
创建管道并连接父子进程(pipe+fork)
【示例程序】
乍看起来,这个使用管道的例子并无特别之处,它做的工作可以用一个简单的文件来完成。管道的真正优势体现在,当你想在两个进程之间传递数据时,程序用fork创建新进程时,原先打开的文件描述符仍将保持打开的状态。如果在原先的进程中创建一个管道,然后再调用fork创建新进程,我们即可通过管道在两个进程之间传递数据。
[父进程] ===file_pipes[1]===> [管道] ===file_pipes[0]===> [子进程]
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; char buffer[BUFSIZ + 1]; pid_t fork_result; memset(buffer,'\0',sizeof(buffer)); if(pipe(file_pipes)==0) { fork_result = fork(); if(fork_result == -1) { fprintf(stderr,"Fork failure"); exit(EXIT_FAILURE); } if(fork_result == 0) { data_processed = read(file_pipes[0],buffer,BUFSIZ); printf("Read %d bytes: %s\n",data_processed,buffer); exit(EXIT_SUCCESS); } else { data_processed = write(file_pipes[1],some_data,strlen(some_data)); printf("Wrote %d bytes\n",data_processed); } } exit(EXIT_SUCCESS); }
创建管道并连接血缘进程(pipe+fork+exec)
【示例程序】
//下面这个程序pipe3.c是管道和exec函数的演示。 #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int length = 0; int pipes[2]; const char data[] = "MONKEY.D.MENG"; char buffer[BUFSIZ + 1]; pid_t child_pid; memset(buffer, 0, sizeof(buffer)); if (pipe(pipes) == 0) { child_pid = fork(); if (child_pid == -1) { fprintf(stderr, "Fork failure!"); return EXIT_FAILURE; } if (child_pid == 0) { sprintf(buffer, "%d", pipes[0]); execl("pipe4", "pipe4", buffer, (char *)0); return EXIT_SUCCESS; } else { length = write(pipes[1], data, strlen(data)); printf("%d - wrote %d bytes/n", getpid(), length); } } return EXIT_SUCCESS; } //“数据消费者”程序pipe4.c负责读取数据,它的代码要简单的多,如下所示: #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { int length = 0; int file_descriptor; char buffer[BUFSIZ + 1]; memset(buffer, 0, sizeof(buffer)); sscanf(argv[1], "%d", &file_descriptor); length = read(file_descriptor, buffer, BUFSIZ); printf("%d -read %d bytes: %s/n", getpid(), length, buffer); return EXIT_SUCCESS; } 编译程序: gcc pipe4.c –o pipe4 gcc pipe3.c –o pipe3 运行程序: ./pipe3 10733 - wrote 13 bytes 10734 -read 13 bytes: MONKEY.D.MENG注意这里的两个文件分别是pipe3.c和pipe4.c。在pipe3中先pipe,这样生成了一个管道(有了两个fd名字),然后再fork,在子进程中exec替换为另一个程序pipe4.c。这个时候在pipe4.c中无法知道管道的端口名字,所以必须要用参数传入:execl("pipe4", "pipe4", buffer, (char *)0);其中的buffer就是传入的参数(端口号)。
创建管道复用标准输入输出连接血缘进程(pipe+fork+dup+exec)
原因在于关闭标准输入0后,再dup第一个分配的将是0.
这些进程还只是由一个共同的祖先进程启动的。而不是不相关的进程(因为是用fork)。
【示例程序】
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main() { int data_processed; int file_pipes[2]; const char some_data[] = "123"; pid_t fork_result; //创建了一个管道,给file_pipes分配了管道的两端的fd值 if (pipe(file_pipes) == 0) { fork_result = fork(); if (fork_result == (pid_t)-1) { fprintf(stderr, "Fork failure"); exit(EXIT_FAILURE); } //子进程 if (fork_result == (pid_t)0) { close(0); //先关闭fd(0)(标准输入) dup(file_pipes[0]); //复制共用file_pipes[0]的另一个fd,会分配到fd(0) close(file_pipes[0]); //关闭file_pipes[0],这样只有fd(0)作为输入了 close(file_pipes[1]); //关闭file_pipes[1],原因是这里子进程只管读出不用输入 execlp("od", "od", "-c", (char *)0); exit(EXIT_FAILURE); } //父进程 else { close(file_pipes[0]); //关闭file_pipes[0],原因是这里父进程只管输入不管读出 data_processed = write(file_pipes[1], some_data, strlen(some_data)); close(file_pipes[1]); //关闭file_pipes[1],使用完毕 printf("%d - wrote %d bytes\n", (int)getpid(), data_processed); } } exit(EXIT_SUCCESS); }
open(const char *path, ORDONLY); 此时,open调用将阻塞,除非有一个进程以写方式打开同一个FIFO,否则它不会返回。这与前面第一个cat命令的例子类似。 open(const char *path, ORDONLY | O_NONBLOCK); 即使没有其他进程以写方式打开FIFO,这个将成功并立刻返回。 open(const char *path, O_WRONLY); 此时,open调用将阻塞,直到有一个进程以读方式打开同一个FIFO为止。 open(const char *path, O_WRONLY | O_NONBLOCK); 这个函数调用总是立刻返回,但如果没有进程以读方式打开FIFO文件,open调用将返回一个错误-1,并且FIFO也不会被打开。如果确实有一个进程以读方式打开FIFO文件,那么我们就可以通过它返回的文件描述符对这个FIFO文件进行写操作。
################client.h #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <limits.h> #define SERVER_FIFO_NAME "/tmp/serv_fifo" #define CLIENT_FIFO_NAME "/tmp/cli_%d_fifo" #define BUFFER_SIZE 20 struct message { pid_t client_pid; char data[BUFFER_SIZE + 1]; };##server.c
#include "client.h" int main() { int client_fifo_fd; int server_fifo_fd; struct message msg; int read_res = 0; char client_fifo[256]; char * tmp_char_ptr; //创建了server端的FIFO有名管道,并以只读方式打开,等待client输入 mkfifo(SERVER_FIFO_NAME, 0777); server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY); if (server_fifo_fd == -1) { fprintf(stderr, "Server fifo failure\n"); return EXIT_FAILURE; } sleep(3); do { //从server的FIFO中读取信息 read_res = read(server_fifo_fd, &msg, sizeof(struct message)); if (read_res > 0) { //将读取的数据转大写处理 tmp_char_ptr = msg.data; while(*tmp_char_ptr){ *tmp_char_ptr = toupper(*tmp_char_ptr); tmp_char_ptr++; } //获取client的FIFO名字,并以写方式打开,写回,关闭 sprintf(client_fifo, CLIENT_FIFO_NAME, msg.client_pid); client_fifo_fd = open(client_fifo, O_WRONLY); if (client_fifo_fd != -1) { write(client_fifo_fd, &msg, sizeof(struct message)); close(client_fifo_fd); } } }while(read_res > 0); //关闭server的FIFO close(server_fifo_fd); unlink(SERVER_FIFO_NAME); return EXIT_SUCCESS; }##client.c
#include "client.h" int main() { int server_fifo_fd; int client_fifo_fd; struct message msg; char client_fifo[256]; //以只写方式打开server端FIFO,这个名字是公共知晓的 server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY); if (server_fifo_fd == -1) { fprintf(stderr, "Sorry, no server\n"); return EXIT_FAILURE; } //定义client端FIFO的名字 msg.client_pid = getpid(); sprintf(client_fifo, CLIENT_FIFO_NAME, msg.client_pid); //创建client端FIFO if (mkfifo(client_fifo, 0777) == -1) { fprintf(stderr, "Sorry, can not make %s\n", client_fifo); return EXIT_FAILURE; } //将数据写入sever端FIFO sprintf(msg.data, "hello world!"); printf("%d sent %s\n", msg.client_pid, msg.data); write(server_fifo_fd, &msg, sizeof(msg)); //以只读方式打开client端FIFO,并关闭 client_fifo_fd = open(client_fifo, O_RDONLY); if (client_fifo_fd != -1) { if (read(client_fifo_fd, &msg, sizeof(msg)) > 0) { printf("received : %s\n", msg.data); } close(client_fifo_fd); } //关闭server端FIFO close(server_fifo_fd); unlink(client_fifo); return EXIT_SUCCESS; }
函数原型: int mkfifo( const char *filename , mode_t mode);【代码示例】
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> int main() { int res = mkfifo("/tmp/my_fifo", 0777); if (res == 0) printf("FIFO created\n"); exit(EXIT_SUCCESS); }
xxx
【无名管道】(函数pipe,公用内存)不属于任何文件系统(不可见),只存在于内存中。适合通过fork来复用(利用fork后父子进程继承了打开的描述符),也正因为用fork,所以才决定了它只能在有亲缘关系的进程间通信。
【有名管道】(函数mkfifo,有inode,但数据块只在内存中)是有名有形的,为了使用这种管道,LINUX中设立了一个专门的特殊文件系统--管道文件,它存在于文件系统中,任何进程可以在任何时候通过有名管道的路径和文件名来访问管道。但是在磁盘上的只是一个节点,而文件的数据则只存在于内存缓冲页面中,与普通管道一样。可以用临时文件来替代么??不能,我觉得有名管道FIFO因为有了打开方式的限制(只读,只写,阻塞,非阻塞的各种组合),使得在阻塞方式下保证了管道两端必须一端是读一端是写,实现了同步!
【文件】(有inode,有硬盘数据块)
最简单的信号量是一个只有0与1两个值的变量,二值信号量。这是最为通常的形式。具有多个正数值的信号量被称之为通用信号量。在本章的其余部分,我们将会讨论二值信号量。
假定我们有一个信号量变量sv,两个操作定义如下: P(sv) 如果sv大于0,减小sv。如果sv为0,挂起这个进程的执行。 V(sv) 如果有进程被挂起等待sv,使其恢复执行。如果没有进行被挂起等待sv,增加sv。
semget函数创建一个新的信号量或是获得一个已存在的信号量键值。
3.3 函数semop
3.4 函数semctl
共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方式,它允许两个不相关的进程访问同一个逻辑内存。共享内存是由IPC为进程创建的一个特殊的地址空间,它将出现在该进程的地址空间中。其他进程可以将同一段共享内存连接到它们自己的地址空间中。对共享内存访问的同步控制必须由程序员来操作。
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符。
函数原型: int shmget(key_t key, size_t size, int shmflg)、 参数说明: key: 0(IPC_PRIVATE):会建立新共享内存对象 大于0的32位整数:视参数shmflg来确定操作。通常源于ftok()返回的IPC键值 size: >0:新建的共享内存大小,以字节为单位 =0:只获取共享内存时指定为0 shmflg: 0:取共享内存标识符,若不存在则函数会报错 IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符 IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错 int: 返回值>0 ,返回共享内存的标识符 返回值=-1,错误原因存于error中
把共享内存区对象映射到调用进程的地址空间
函数原型: void *shmat(int shmid, const void *shmaddr, int shmflg) 参数说明: msqid: 共享内存标识符 shmaddr: 指定共享内存出现在进程内存地址的什么位置 直接指定为NULL让内核自己决定一个合适的地址位置 shmflg: SHM_RDONLY:为只读模式,其他为读写模式 void*: 成功:附加好的共享内存地址 出错:-1,错误原因存于error中
与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
函数原型: int shmdt(const void *shmaddr) 参数说明: shmaddr: 连接的共享内存的起始地址 int:返回值 成功:0 出错:-1,错误原因存于error中
完成对共享内存的控制
函数原型: int shmctl(int shmid, int cmd, struct shmid_ds *buf) 参数说明: msqid: 共享内存标识符 cmd: IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中 IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内 IPC_RMID:删除这片共享内存 buf: 共享内存管理结构体。具体说明参见共享内存内核结构定义部分 int:函数返回值 成功:0 出错:-1,错误原因存于error中
消息队列提供一种在两个不相关的进程之间传递数据的相当简单且有效的方法。消息队列独立于发送和接收进程存在,消除了同步命名管道的打开和关闭时可能产生的一些困难。
5.2 函数msgget
5.3 函数msgsnd
5.4 函数msgrcv
5.5 函数msgctl