1、基本概念
进程是指正在运行的程序,一个程序中可以包含多个进程;一个进程可能包含一个或者多
个线程。
top
进程ID
• 每一个进程都有一个唯一的标识符,进程ID 简称pid
– 进程id 一般默认的最大值为32768,不过也是可以修改的,当然一般情况下不需要这么做。如果当前进程是1000,那么下一个分配的进程就是1001,它是严格线性分配的
– 除了init 进程,其它进程都是由其它进程创立的。创立新进程的进程叫父进程,新进程叫子进程
• man 2 getpid
• man 2 getppid
• 获取子进程的函数
• pid_t getpid(void)
– 参数:无
– 返回值:成功返回进程号
• 获取父进程的函数
• pid_t getppid(void);
– 参数:无
– 返回值:成功返回父进程
getpid.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void main()
{
pid_t idp,id;
idp = getppid();
printf("ppid = %d\n",idp);
id = getpid();
printf("pid = %d\n",id);
}
2、exec 函数族
linux中,可以使用exec函数族将程序载入内存,实现多个程序的运行
• man 3 exec
• exec函数族参数
– “l”和“v”表示参数是以列表还是以数组的方式提供的
– “p”表示这个函数的第一个参数是*path,就是以绝对路径来提供程序的路径,也可以以当前目录作为目标
– “e”表示为程序提供新的环境变量
execl.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <unistd.h>
//exec函数族
int main(void)
{
if(execl("/mnt/udisk/helloexec","helloexec","execl",NULL) == -1){
perror("execl error");
exit(1);
}
//程序已经跳转走,如果正常execl不反回错误,下面的代码不会执行!
printf("execl error!\n");
return 0;
}
helloexec.c
#include <stdio.h>
int main(int arc,char *argv[])
{
printf("Hello %s!\n",argv[1]);
}
Hello execl
3、fork 创建新工程
• linux中,可以使用fork函数创建和当前进程一模一样的进程,叫子进程,原来的进程叫父进程
• man 2 fork
• 创建进程函数
• pid_t fork(void);
– 参数:无
– 返回值:执行成功,子进程pid 返回给父进程,0 返回给子进程;出现错误-1,返回给父进程。执行失败的唯一情况是内存不够或者id 号用尽,不过这种情况几乎很少发生。
• fork函数
– 系统函数fork 调用成功,会创建一个新的进程,它几乎会调用差不多完全一样的fork 进程
– 子进程的pid 和父进程不一样,是新分配的
– 子进程的ppid 会设置为父进程的pid,也就是说子进程和父进程各自的“父进程”不一样
– 子进程中的资源统计信息会清零
– 挂起的信号会被清除,也不会被继承
– 所有文件锁也不会被子进程继承
fork.c
#include <stdio.h>
#include <unistd.h>
main()
{
pid_t pid;
int i=100;
pid = fork();
//调用出错
if(pid == -1){
printf("fork failed\n");
return 1;
}
//返回给父进程子进程号,返回值大于0
else if(pid){
i++;
printf("\nThe father i = %d\n",i);
printf("The father return value is %d\n",pid);
printf("The father pid is %d\n",getpid());
printf("The father ppid is %d\n",getppid());
while(1);
}
//返回子进程0,返回值等于0返回给子进程
else{
i++;
printf("\nThe child i = %d\n",i);
printf("The child return value is %d\n",pid);
printf("The child pid is %d\n",getpid());
printf("The child ppid is %d\n",getppid());
while(1);
}
return 0;
}
4、综合例程
• linux命令的本质
– linux命令的本质是一些小程序
• 综合例程
– 包含fork以及所有的exec函数族
execls.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
char *arg[] = {"ls","-a",NULL};
if(fork() == 0){
//in child1
printf("fork1 is OK;execl\n");
if(execl("/bin/ls","ls","-a",NULL) == -1){
perror("execl error");
exit(1);
}
}
usleep(20000);
if(fork() == 0){
//in child2
printf("fork2 is OK;execv\n");
if(execv("/bin/ls",arg) == -1){
perror("execv error");
exit(1);
}
}
usleep(20000);
if(fork() == 0){
//in child3
printf("fork3 is OK;execlp\n");
if(execlp("ls","ls","-a",NULL) == -1){
perror("execlp error");
exit(1);
}
}
usleep(20000);
if(fork() == 0){
//in child4
printf("fork4 is OK;execvp\n");
if(execvp("ls",arg) == -1){
perror("execvp error");
exit(1);
}
}
usleep(20000);
if(fork() == 0){
//in child5
printf("fork5 is OK;execle\n");
if(execle("/bin/ls","ls","-a",NULL,NULL) == -1){
perror("execle error");
exit(1);
}
}
usleep(20000);
if(fork() == 0){
//in child6
printf("fork6 is OK;execve\n");
if(execve("/bin/ls",arg,NULL) == -1){
perror("execve error");
exit(1);
}
}
//加入小延时可以避免发生混乱的情况
usleep(20000);
return 0;
}
5、无名管道
• 无名管道有一定的局限性
– 它是属于半双工的通信方式
– 只有具有“亲缘关系”的的进程才能使用这种通信方式,也就是父进程和子进程之间
• man 2 pipe
• int pipe(int pipefd[2])
– 参数pipefd[0]:用于读管道
– 参数pipefd[1]:用于写管道
– 返回值:执行成功返回0,失败返回-1
• man 7 pipe
– 官方文档中的例程
// write函数的两种用法
int write(int handle, void *buf, int nbyte);
像指定的缓冲区传数据,handle是文件描述符,buf指向内存的指针,nbyte为指定的字节数;返回值为-1则出错
write(const char* str,int n);
read函数
int read(int handle, void *buf, int nbyte);
read()会把参数handle所指的文件传送nbyte个字节到buf指针所指的内存中。若参数nbyte为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或无可读取的数据。
pipe.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
//进程读函数
void read_data(int *);
//进程写函数
void write_data(int *);
int main(int argc,char *argv[])
{
int pipes[2],rc;
pid_t pid;
rc = pipe(pipes); //创建管道
if(rc == -1){
perror("\npipes\n");
exit(1);
}
pid = fork(); //创建进程
switch(pid){
case -1:
perror("\nfork\n");
exit(1);
case 0:
read_data(pipes); //相同的pipes
default:
write_data(pipes); //相同的pipes
}
return 0;
}
//进程读函数
void read_data(int pipes[])
{
int c,rc;
//由于此函数只负责读,因此将写描述关闭(资源宝贵)
close(pipes[1]);
//阻塞,等待从管道读取数据
//int 转为 unsiged char 输出到终端
while( (rc = read(pipes[0],&c,1)) > 0 ){
putchar(c);
}
exit(0);
}
//进程写函数
void write_data(int pipes[])
{
int c,rc;
//关闭读描述字
close(pipes[0]);
while( (c=getchar()) > 0 ){
rc = write( pipes[1], &c, 1); //写入管道
if( rc == -1 ){
perror("Parent: write");
close(pipes[1]);
exit(1);
}
}
close( pipes[1] );
exit(0);
}
6、有名管道
• 无名管道只能用于有亲缘关于的进程通信,有名管道可以实现无亲缘关系的通信
• 有名管道fifo 给文件系统提供一个路径,这个路径和管道关联,只要知道这个管道路径,就可以进行文件访问,fifo 是指先进先出,也就是先写入的数据,先读出来
• 有名管道的读写速度非常快
//PIPE_BUF,limits.h常量
int access(const char * pathname, int mode)判断权限
//mode常量有R_OK,W_OK,X_OK 和F_OK。
//R_OK,W_OK与X_OK用来检查文件是否具有读取、写入和执行的权限;F_OK则是用来判断该文件是否存在。
int mkfifo(const char * pathname,mode_t mode);建立FIFO文件,
//权限0777,八进制数,最高权限了。第一个7代表文件所有者的权限,第二个7代表所属用户组的权限,第三个7代表其它用户的权限。7是由r 4 w2 x 1组成。
//mode_t 常量在头文件 fcntl.h中,O_RDONLY 、 O_WRONLY 、 O_RDWR 、 O_CREAT……
• man 3 mkfifo
• int mkfifo(const char *pathname, mode_t mode)
– 参数*pathname:路径名,管道名称
– 参数mode:管道的权限
– 返回值:成功返回0,错误返回-1
creatc.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void filecopy(FILE *,char *);
int main(void)
{
FILE *fp1;
long int i = 100000;
char buf[] = "I want to study Linux!\n";
char *file1 = "data.txt";
printf("begin!\n");
if((fp1 = fopen(file1,"a+")) == NULL ){
printf("can't open %s\n",file1);
}
while(i--)
filecopy(fp1,buf);
fclose(fp1);
printf("over!\n");
return 0;
}
void filecopy(FILE *ifp,char *buf)
{
char c;
int i,j;
j = 0;
i = strlen(buf)-1;
while(i--){
putc(buf[j],ifp);
j++;
}
putc('\n',ifp);
}
writepipe.c
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
int main()
{
const char *fifo_name = "my_fifo";
char *file1 = "data.txt";
int pipe_fd = -1;
int data_fd = -1;
int res = 0;
const int open_mode = O_WRONLY;
int bytes_sent = 0;
char buffer[PIPE_BUF + 1];
if(access(fifo_name, F_OK) == -1)
{
//管道文件不存在
//创建命名管道
res = mkfifo(fifo_name, 0777);
if(res != 0)
{
fprintf(stderr, "Could not create fifo %s\n", fifo_name);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n", getpid());
//以只写阻塞方式打开FIFO文件,以只读方式打开数据文件
pipe_fd = open(fifo_name, open_mode);
data_fd = open(file1, O_RDONLY);
printf("Process %d result %d\n", getpid(), pipe_fd);
if(pipe_fd != -1)
{
int bytes_read = 0;
//向数据文件读取数据
bytes_read = read(data_fd, buffer, PIPE_BUF);
buffer[bytes_read] = '\0';
while(bytes_read > 0)
{
//向FIFO文件写数据
res = write(pipe_fd, buffer, bytes_read);
if(res == -1)
{
fprintf(stderr, "Write error on pipe\n");
exit(EXIT_FAILURE);
}
//累加写的字节数,并继续读取数据
bytes_sent += res;
bytes_read = read(data_fd, buffer, PIPE_BUF);
buffer[bytes_read] = '\0';
}
close(pipe_fd);
close(data_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished\n", getpid());
exit(EXIT_SUCCESS);
}
readpipe.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <string.h>
int main()
{
const char *fifo_name = "my_fifo";
int pipe_fd = -1;
int data_fd = -1;
int res = 0;
int open_mode = O_RDONLY;
char buffer[PIPE_BUF + 1];
int bytes_read = 0;
int bytes_write = 0;
//清空缓冲数组
memset(buffer, '\0', sizeof(buffer));
printf("Process %d opening FIFO O_RDONLY\n", getpid());
//以只读阻塞方式打开管道文件,注意与fifowrite.c文件中的FIFO同名
pipe_fd = open(fifo_name, open_mode);
//以只写方式创建保存数据的文件
data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644);
printf("Process %d result %d\n",getpid(), pipe_fd);
if(pipe_fd != -1)
{
do
{
//读取FIFO中的数据,并把它保存在文件DataFormFIFO.txt文件中
res = read(pipe_fd, buffer, PIPE_BUF);
bytes_write = write(data_fd, buffer, res);
bytes_read += res;
}while(res > 0);
close(pipe_fd);
close(data_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
exit(EXIT_SUCCESS);
}
7、消息队列
• 消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级
• 对消息队列有写权限的进程可以向其中按照一定的规则添加新消息;
对消息队列有读权限的进程则可以从消息队列中读走消息。
//BUFSIZ 常量
int msgget(key_t key, int msgflg)
msgid = msgget((key_t)1234, 0666 | IPC_CREAT)
// 消息队列对象的关键字key
// msgflg,标识符和权限(0666 | IPC_CREAT)
int fprintf(FILE*stream,constchar*format,[argument])
fprintf(stderr, "msgget failed with error: %d\n", errno)
// stderr是输出的目标流文件,它和stdin和stdout类似,都可以显示在屏幕
// errno是最后一个错误的编号
• man 2 msgrcv
• 函数ssize_t msgrcv(int msqid, void*msgp, size_t msgsz, long msgtyp,int msgflg)
msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0)
// msqid:消息队列的识别码
// msgp:指向消息缓冲区的指针
// msgsz:消息的大小
// msgtyp:消息类型(msgtyp等于0 则返回队列的最早的一个消息;大于0,则返回其类型为mtype的第一个消息;小于0,则返回其类型小于或等于mtype参数的绝对值的最小的一个消息)
// msgflg:0表示忽略
strncmp、msgctl
int strncmp(char *str1, char *str2, int maxlen)
strncmp(data.text, "end", 3)
int void msgctl(int msqid,int cmd,struct msqid_ds *buf)
msgctl(msgid, IPC_RMID, 0)
// msqid: 队列ID
// cmd: IPC_RMID移走内核中的消息队列
// msqid_ds:描述当前队列状态的结构体
msgsend
struct msg_st;struct msg_st data;
long int msg_type类型
char text[MAX_TEXT]数据
char *fgets(char *buf, int bufsize, FILE *stream)
// buf:指向用来存储所得数据的地址
// bufsize:指明存储数据的大小
// *stream:文件结构体指针,将要读取的文件流
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
msgsnd(msgid, (void*)&data, MAX_TEXT, 0)
// msqid:消息队列的识别码
// msgp:指向消息缓冲区的指针
// msgsz:消息的大小
// msgflg:控制函数行为的标志
• 结构体msgp,是一个标准的通用结构
struct msgstru{
long mtype; //大于0
char mtext[nbyte];}
int msgget(key_t key, int msgflg)
– 参数“key”:消息队列关联的标识符
– 参数“msgflg”:消息队列的建立标志和存取权限。IPC_CREAT 如果内核中没有此队列则创建它;IPC_EXCL 当和IPC_CREAT 一起使用时,如果队列已经存在,则失败
– 返回值:执行成功则返回消息队列的标识符,否则返回-1
msgsend.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
#define MAX_TEXT 512
struct msg_st
{
long int msg_type;
char text[MAX_TEXT];
};
int main()
{
int running = 1;
struct msg_st data;
char buffer[BUFSIZ];
int msgid = -1;
//建立消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
//向消息队列中写消息,直到写入end
while(running)
{
//输入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
data.msg_type = 1; //注意2
strcpy(data.text, buffer);
//向队列发送数据
if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsnd failed\n");
exit(EXIT_FAILURE);
}
//输入end结束输入
if(strncmp(buffer, "end", 3) == 0)
running = 0;
sleep(1);
}
exit(EXIT_SUCCESS);
}
msgreceive.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
struct msg_st
{
long int msg_type;
char text[BUFSIZ];
};
int main()
{
int running = 1;
int msgid = -1;
struct msg_st data;
long int msgtype = 0; //注意1
//建立消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
//从队列中获取消息,直到遇到end消息为止
while(running)
{
if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
{
fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
exit(EXIT_FAILURE);
}
printf("You wrote: %s\n",data.text);
//遇到end结束
if(strncmp(data.text, "end", 3) == 0)
running = 0;
}
//删除消息队列
if(msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
8、signal
• 信号用于处理异步事件,信号的通信方式理解起来还是有一定难度的,
它既可以在一个进程内进行通信,发送信号给进程,又可以用于进程
外通信
• man 2 alarm
• man 2 signal
sighelloc.c
alarm 函数 闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它向进程发送SIGALRM信号。如果忽略或者不捕获此信号,则其默认动作是终止调用该alarm函数的进程。
unsigned int alarm(unsigned int seconds)
– 参数seconds:闹钟的时间,单位为秒
– 返回值:成功返回0 或者返回剩余时间;错误返回-1
sig_t signal(int signum,sig_t handler)
signal(SIGALRM,handler);
// 第一个参数 signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号。
// 第二个参数 handler描述了与信号关联的动作。
• 以下情况会产生信号
– 按下按键;硬件异常;kill函数或者命令等
• 常见信号
– SIGALRM:闹钟
– SIGHUP:终端发出的结束信号
– SIGINT:键盘的ctrl+c
– SIGKILL:kill命令产生的信号
– SIGSTOP:键盘ctrl+z
sigset.c
sigset_t
typedef struct {unsigned long sig[_NSIG_WORDS];} sigset_t
无符号长整型数,定义信号集
_NSIG_WORDS
sigaction
struct sigaction act
// 查询或设置信号处理方式
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;int sa_flags;
void (*sa_restorer)(void);}
int sigemptyset(sigset_t *set);
sigemptyset(&sigset)
// 初始化信号集
int sigaddset(sigset_t *set,int signum);
sigaddset(&sigset, SIGINT)
// 将参数signum 代表的信号加入至参数set 信号集里
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
...sigaction(SIGINT, &act, 0)...
// 设置处理函数和信号集
pause()
挂起进程,等待信号,让进程暂停直到信号出现
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oldset);
// 检测或改变目前的信号屏蔽字
sigprocmask(SIG_SETMASK, &sigset, 0);
屏蔽SIGINT
int sigismember(const sigset_t *set,int signum)
// 用来测试参数signum 代表的信号是否已加入至参数set信号集里。如果信号集里已有该信号则返回1,否则返回0。如果有错误则返回-1
sigismember(&ign, SIGINT)
int sigdelset(sigset_t * set,int signum);
sigdelset(&sigset, SIGINT)
// 用来将参数signum代表的信号从参数set信号集里删除
int sigpending(sigset_t *set)返回在送往进程的时候被阻塞挂起的信号集合
sigpending(&ign)
• 函数pause
– 用于捕捉进程挂起直到捕捉到信号
sig_hello.c
#include<unistd.h>
#include<stdio.h>
#include<signal.h>
void handler()
{
printf("hello\n");
}
int main(void)
{
int i;
signal(SIGALRM, handler);
alarm(5);
for(i=1;i<7;i++){
printf("sleep %d....\n",i);
sleep(1);
}
return 0;
}
sigset.c
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
void handler(int sig)
{
printf("Handler the signal %d\n", sig);
}
int main(void)
{
sigset_t sigset;//用于记录屏蔽字
sigset_t ign;//用于记录被阻塞的信号集
struct sigaction act;
//清空信号集
sigemptyset(&sigset); //初始化信号集
sigemptyset(&ign);
//向信号集中添加信号SIGINT
sigaddset(&sigset, SIGINT);
//设置处理函数和信号集
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
printf("Wait the signal SIGINT...\n");
pause();//挂起进程,等待信号
//设置进程屏蔽字,在本例中为屏蔽SIGINT
sigprocmask(SIG_SETMASK, &sigset, 0);
printf("Please press Ctrl+c in 10 seconds...\n");
sleep(10);
//测试SIGINT是否被屏蔽
sigpending(&ign);
if(sigismember(&ign, SIGINT))
printf("The SIGINT signal has ignored\n");
//在信号集中删除信号SIGINT
sigdelset(&sigset, SIGINT);
printf("Wait the signal SIGINT...\n");
//将进程的屏蔽字重新设置,即取消对SIGINT的屏蔽
//并挂起进程
sigsuspend(&sigset);
printf("The app will exit in 5 seconds!\n");
sleep(5);
exit(0);
}
sigset程序执行流程
默认情况下,Ctrl+c是直接退出程序。Ctrl+c是产生一个信号SIGINT,可以修改程序捕获这个信号的行为。程序先执行修改捕获SIGINT后的行为,然后使用pause()等待信号出现,输入Ctrl+c产生SIGINT,执行handler函数。接着将信号屏蔽,这种情况下,Ctrl+c这种退出程序的输入就是无效的。10秒后,检测信号是否被屏蔽,接着取消屏蔽字,换一种方式处理信号即堵塞方式,5秒后程序自动结束
9、信号量
信号与信号量是不同的两种事物。
信号量是用来调协进程对共享资源的访问的。为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行,而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它。
man 2 semget
union semum
union semun { int val; struct semid_ds *buf; unsigned short *arry; }
以一种数据类型存储数据,以另一种数据类型来读取数据
int semget(key_t key,int nsems,int semflg)
semget((key_t)1234, 1, 0666 | IPC_CREAT)
参数key不相关的进程可以通过它访问一个信号量,只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符
参数num_sems指定需要的信号量数目,它的值几乎总是1
第三个参数sem_flags是一组标志,权限+创建(如果不存在则创建,存在则打开)
semget使用的时候建立的结构体
struct sembuf{short sem_num;short sem_op;short sem_flg;}
sem_num除非使用一组信号量,否则它为0
sem_op操作时需要改变的数据,-1即P等待,+1即V发送信号
sem_flg通常为SEM_UNDO,使操作系统跟踪信号
int semop(int semid,struct sembuf *sops,size_t nsops)
semop(sem_id, &sem_b, 1)PV 操作通过调用semop函数来实现
semid:信号集的识别码
sops:指向存储信号操作结构的数组指针
nsops:信号操作结构的数量,恒大于或等于1
int semctl(int semid,int semnum,int cmd, /*union semun arg*/)
semctl(sem_id, 0, SETVAL, sem_union)系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。
semid信号量集IPC标识符
semnum操作信号在信号集中的编号
cmd指出要操作的具体命令,SETVAL设置信号量集中的一个单独的信号量的值,IPC_RMID将信号量集从内存中删除
sem_union指向内核中使用的数据结构的指针
rand()
rand()函数是产生随机数的一个随机函数
fflush()
fflush() 清除读写缓冲区,需要立即把输出缓冲区的数据进行物理写入时缓冲区有stdin和stdout
seml 中主要有下面几种操作,
建立信号量,初始化信号量,进入P(sv)等待状态,发送信号V(sv),删除信号量
seml是两个代码同样的程序运行。
第一次运行的时候,因为不存在该信号量所以要初始化信号量,这样我们输入的信息,将会在第一次运行的程序中打印出来;
第二次运行的时候,因为已经存在该信号量,所以打印出来的是默认的’X’字符
seml.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
static int sem_id = 0;
static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();
int main(int argc, char *argv[])
{
char message = 'X';
int i = 0;
//创建信号量
sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
if(argc > 1) {
//程序第一次被调用,初始化信号量
if(!set_semvalue()) {
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
//设置要输出到屏幕中的信息,即其参数的第一个字符
message = argv[1][0];
sleep(2);
}
for(i = 0; i < 10; ++i) {
//进入临界区
if(!semaphore_p())
exit(EXIT_FAILURE);
//向屏幕中输出数据
printf("%c", message);
//清理缓冲区,然后休眠随机时间
fflush(stdout);
sleep(rand() % 3);
//离开临界区前再一次向屏幕输出数据
printf("%c", message);
fflush(stdout);
//离开临界区,休眠随机时间后继续循环
if(!semaphore_v())
exit(EXIT_FAILURE);
sleep(rand() % 2);
}
sleep(10);
printf("\n%d - finished\n", getpid());
if(argc > 1) {
//如果程序是第一次被调用,则在退出前删除信号量
sleep(3);
del_semvalue();
}
exit(EXIT_SUCCESS);
}
static int set_semvalue()
{
//用于初始化信号量,在使用信号量前必须这样做
union semun sem_union;
sem_union.val = 1;
if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
return 0;
return 1;
}
static void del_semvalue()
{
//删除信号量
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
fprintf(stderr, "Failed to delete semaphore\n");
}
static int semaphore_p()
{
//对信号量做减1操作,即等待P(sv)
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;//P()
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_p failed\n");
return 0;
}
return 1;
}
static int semaphore_v()
{
//这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;//V()
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_v failed\n");
return 0;
}
return 1;
}
10、共享内存
共享内存是进程间通信中最简单的方式之一。共享内存在各种进程间通信方式中具有最高的效率。因为系统内核没有对访问共享内存进行同步,您必须提供自己的同步措施。解决这些问题的常用方法是通过使用信号量进行同步。
shmdata.h
struct shared_use_st { int written; char text[TEXT_SZ]; };
// written作为一个标志,非0:表示可读,0表示可写
// text记录写入和读取的文本
shmread.c
int shmget(key_t key, size_t size, int shmflg)
shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT)
// shmget 得到一个共享内存标识符或创建一个共享内存对象
// key 建立新的共享内存对象
// size 新建立的内存大小
// shmflg 标识符
void *shmat(int shmid, const void *shmaddr, int shmflg)
shmat(shmid, 0, 0)
// 返回共享的内存地址,否则返回-1
// shmid 共享内存标识符
// shmaddr 指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
// shmflg SHM_RDONLY:为只读模式,其他为读写模式
shm == (void*)-1
// 将-1转化为指针地址,这样书写便于移植
// 设置共享内存
shared = (struct shared_use_st*)shm
shared->written = 0
int shmdt(const void *shmaddr)
shmdt(shm) == -1
// shmaddr:连接的共享内存的起始地址
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shmctl(shmid, IPC_RMID, 0) == -1
// shmid共享内存标识符
// cmd IPC_RMID:删除这片共享内存
// buf共享内存管理结构体
注意查看文件中函数shmget调用,两个程序通过(key_t)1234这个变量建立联系。如果我们将两个程序中的变量修改成不一样的了,那么两个程序间就将无法进行通信了。
读程序建立共享内存空间,写程序取得共享内存空间的地址
-写程序等待控制台输入,检测到有输入就向共享内存中写数据,然后设置共享内存段可读
-读程序检测内存段是否可读,如果不可读,延时1秒继续检测。检测到可读,读取数据,并且输出,
-写程序检测到end字符,将数据发送到共享内存,然后将共享内存从进程中分离,退出
-读程序检测到内存段有end,输出,然后将共享内存从进程中分离,删除共享内存,退出
shmdata.h
#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER
#define TEXT_SZ 2048
struct shared_use_st
{
//作为一个标志,非0:表示可读,0表示可写
int written;
//记录写入和读取的文本
char text[TEXT_SZ];
};
#endif
shmwrite.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shmdata.h"
int main(void)
{
int running = 1;
void *shm = NULL;
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ + 1];//用于保存输入的文本
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, (void*)0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %p\n", shm);
//设置共享内存
shared = (struct shared_use_st*)shm;
while(running)//向共享内存中写数据
{
//数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
while(shared->written == 1)
{
sleep(1);
printf("Waiting...\n");
}
//向共享内存中写入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared->text, buffer, TEXT_SZ);
//写完数据,设置written使共享内存段可读
shared->written = 1;
//输入了end,退出循环(程序)
if(strncmp(buffer, "end", 3) == 0)
running = 0;
}
//把共享内存从当前进程中分离
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
}
shmwrite.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include "shmdata.h"
int main(void)
{
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 %p\n", 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);
}