文章目录
一、进程pid获取函数
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
- 返回值:返回进程号。
pid_t getppid(void);
- 返回值:返回父进程号。
示例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
pid_t idp, id;
idp = getppid();
printf("ppid = %d\n", idp);
id = getpid();
printf("pid = %d\n", id);
return 0;
}
二、exec函数簇
#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 execvpe(const char *file, char *const argv[], char *const envp[]);
参数说明:
- *path:文件的绝对路径。
- *file:文件名,系统会自动进行搜索其路径。
- *arg:参数以列表形式传递,使用时需要把参数逐一输入,列表最后一个参数必须是NULL。
- argv[]:参数以数组形式传递,使用时需要把参数保存在数组里,数组最后一个参数必须是NULL。
- envp[]:新的环境变量,不需要是传递NULL即可。
返回值:
- 返回0则成功,否则发生错误。
函数名的说明:
- “l”和“v”表示参数是以列表还是以数组的方式提供的。
- “p”表示这个函数的第一个参数是*file,就是以绝对路径来提供程序的路径,也可以把当前目录作为目标。
- “e”表示为程序提供新的环境变量。
示例:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
char *arg[] = {"ls", "-a", NULL};//exec簇函数传入参数要以NULL为结尾
if(fork() == 0)//子线程1
{
printf("fork1 succeed.\nexecl test\n");
if(execl("/bin/ls", "ls", "-a", NULL) == -1)//发生错误
{
perror("execl error.\n");
exit(1);
}
}
usleep(20000);
if(fork() == 0)//子线程2
{
printf("\n\nfork2 succeed.\nexecv test\n");
if(execv("/bin/ls", arg) == -1)
{
perror("execv error.\n");
exit(1);
}
}
usleep(20000);
if(fork() == 0)//子线程3
{
printf("\n\nfork3 succeed.\nexeclp test\n");
if(execlp("ls", "ls", "-a", NULL) == -1)
{
perror("execp error.\n");
exit(1);
}
}
usleep(20000);
if(fork() == 0)//子线程4
{
printf("\n\nfork4 succeed.\nexecvp test\n");
if(execvp("ls", arg) == -1)
{
perror("execvp error.\n");
exit(1);
}
}
usleep(20000);
if(fork() == 0)//子线程5
{
printf("\n\nfork5 succeed.\nexecle test\n");
if(execle("/bin/ls", "ls", "-a", NULL, NULL) == -1)
{
perror("execle error.\n");
exit(1);
}
}
usleep(20000);
if(fork() == 0)//子线程6
{
printf("\n\nfork6 succeed.\nexecve test\n");
if(execve("/bin/ls", arg, NULL) == -1)
{
perror("execve error.\n");
exit(1);
}
}
usleep(20000);
return 0;
}
上面建立的每个线程都可实现在程序内执行ls -a
的指令。
三、进程获取函数
#include <unistd.h>
pid_t fork(void);
返回值:
- 成功则返回0给子进程,同时返回父进程子进程号(返回值大于0),返回-1则发生错误。
调用该函数的父进程和被创建的子进程的关系:
- 系统函数fork调用成功,会创建一个新的进程。
- 子进程的ppid会设置为父进程的pid,也就是说子进程和父进程各自的“父进程”不一样。
- 子进程中的资源统计信息会清零。
- 挂起的信号会被清除,也不会被继承。
- 所有文件锁也不会被子进程继承。
示例:
#include <unistd.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
pid_t pid;
int i=100;
pid = fork();
if(pid == -1)//出错
{
printf("fork failed\n");
return 1;
}
else if(pid)//返回父进程子进程号,返回值大于0
{
i++;
printf("parent i is %d\n", i);
printf("parent return value is %d\n", pid);
printf("parent pid is %d\n", getpid());
printf("parent ppid is %d\n", getppid());
while(1);
}
else//返回子进程,返回值等于0
{
i++;
printf("child i is %d\n", i);
printf("child return value is %d\n", pid);
printf("child pid is %d\n", getpid());
printf("child ppid is %d\n", getppid());
}
return 0;
}
返回值如下:
parent i is 101 //父进程中i的值
parent return value is 3584 //子进程pid
parent pid is 3583 //父进程的pid
parent ppid is 2391 //父进程的ppid
child i is 101 //子进程中i的值
child return value is 0 //返回0给子进程
child pid is 3584 //子进程pid
child ppid is 3583 //子进程的ppid
四、无名管道
无名管道只能用于父进程和子进程间的通讯,而且是半双工通讯方式。
#include <unistd.h>
int pipe(int pipefd[2]);
参数说明:
- pipefd[2]:传入一个可容纳两个元素的数组,用于存储函数返回的两个管道文件描述符,其中pipefd[0]用于存储读管道的文件描述符,pipefd[1]用于存储写管道的文件描述符。
返回值:
- 成功则返回0,若返回-1则发生错误。
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <unistd.h>
int pipe2(int pipefd[2], int flags);
函数说明:
- 如果flag为0,则pipe2和pipe函数相同;flag为O_NONBLOCK时使I/O变成非阻塞模式;flag为O_CLOEXEC时为新的文件描述符启用close-on-exec标志。
示例如下,该实例通过无名管道实现数据回显:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
//进程读函数
void read_data(int *);
//进程写函数
void write_data(int *);
//无名管道只能用于父进程和子进程间的通讯,而且是半双工通讯方式。
int main(int argc, char* argv[])
{
int pipes[2], ret;
pid_t pid;
ret = pipe(pipes);//创建管道
if(ret == -1)
{
perror("pipe error\n");
exit(1);
}
pid = fork();//创建进程
switch(pid)
{
case -1:
perror("fork error\n");
exit(1);
case 0://子进程执行
read_data(pipes);
default://父进程执行
write_data(pipes);
}
return 0;
}
//进程读函数
void read_data(int pipes[])
{
int ch, ret;
close(pipes[1]);//关闭写管道
while( (ret = read(pipes[0], &ch, 1)) > 0 )
{
putchar(ch);
}
exit(0);
}
//进程写函数
void write_data(int pipes[])
{
int ch, ret;
close(pipes[0]);//关闭读管道
while( (ch = getchar()) > 0 )
{
ret = write( pipes[1], &ch, 1);//写入管道
if(ret == -1)
{
perror("parent write");
close(pipes[1]);//关闭写管道
exit(1);
}
}
close(pipes[1]);//关闭写管道
exit(0);
}
五、有名管道
(一)、生成数据文件
下面的程序用于生成数据文件data.txt,里面含有100000个“I love Linux!\n”,用于下面FIFO的读写。
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
void filecopy(FILE *, char *);
int main(int argc, char* argv[])
{
FILE *fp1;
long int i = 100000;
char buf[] = "I love Linux!";
char *file1 = "data.txt";
printf("create fifo test begins\n");
if((fp1 = fopen(file1, "a+")) == NULL)
{
printf("open file %s failed\n", file1);
}
while(i--)
filecopy(fp1, buf);
fclose(fp1);
printf("create fifo test is over\n");
return 0;
}
void filecopy(FILE *fp, char *buf)
{
char c;
int i, j=0;
i = strlen(buf) - 1;
while(i--)
{
putc(buf[j], fp);
j++;
}
putc('\n', fp);
}
(二)、FIFO写入函数
下面的程序用于将data.txt文件里面的数据写入到有名管道fifo,等待读取管道读取。
在实际运行时需要在指令后面添加’&'让程序在后台执行(如./process_fifo_write &)。
可通过指令’jobs’查看在后台运行中的程序。
执行完写入fifo之后,会发现新增了一个名为my_fifo的文件,这就是新创建的有名管道文件。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdlib.h>
#include <limits.h>
int main(int argc, char* argv[])
{
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_send = 0;
char buffer[PIPE_BUF + 1];
if(access(fifo_name, F_OK) == -1)
{
res = mkfifo(fifo_name, 0777);
if(res != 0)
{
fprintf(stderr, "Couldn't create fifo %s\n", fifo_name);
exit(EXIT_FAILURE);
}
}
printf("process %d opening FIFO O_WRONLY\n", getpid());
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 pipe error\n");
exit(EXIT_FAILURE);
}
//累计写的字节数,并继续读取数据
bytes_send += 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());
return 0;
}
(三)、FIFO读取函数
调用读管道读取有名管道fifo中的数据,并将其写入到文件DataFromFIFO.txt中·。
#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(int argc, char* argv[])
{
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("DataFromFIFO.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);
return 0;
}
六、消息队列
(一)、消息队列创建函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数说明:
- key:消息队列关联的标识符。
- msgflg:消息队列的建立标志和存取权限。IPC_CREAT:如果没有该消息队列则创建。IPC_CREAT | IPC_EXCL 一起使用时,如果队列存在,则创建失败。
返回值:
- 执行成功则返回消息队列的标识符,错误则返回-1。
(二)、消息队列操作函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数说明:
- msqid:消息队列的标识码。
- cmd:操作指令。
IPC_STAT: 将与msqid关联的内核数据结构中的信息复制到buf所指向的msqid_ds结构中。调用者必须具有消息队列上的读权限。
IPC_SET:将buf指向的msqid_ds结构的一些成员的值写入与此消息队列关联的内核数据结构,并更新其msg_ctime成员。
IPC_RMID:立即删除消息队列,唤醒所有正在等待的读写器进程
- *buf:msqid_ds结构体。
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
返回值:
- 返回-1则发生错误,函数执行正确的返回值需要根据cmd来确定。
(三)、发送消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数声明:
- msqid:消息队列的标识码。
- *msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构。
- msgsz:消息的长短。
- msgflg:标志位。
返回值:
- 成功则返回0,错误则返回-1。
消息缓冲区的结构如下:
struct msgbuf {
long mtype; /* message type, must be > 0 消息类型,必须大于0 */
char mtext[msgsz]; /* message data 存放消息的数据 */
};
示例:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#define MAX_LENGTH 512
struct msg_st
{
long int msg_type;
char text[MAX_LENGTH];
};
int main(int argc, char* argv[])
{
int running = 1;
struct msg_st msg_queue;
char buffer[MAX_LENGTH];
int msgid = -1;
//创建消息队列
msgid = msgget((key_t)1234, IPC_CREAT | 0666);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error code : %d\n", errno);
exit(EXIT_FAILURE);
}
//向消息队列写消息
while(running)
{
printf("Enter some message:");
fgets(buffer, MAX_LENGTH, stdin);
msg_queue.msg_type = 1;
strcpy(msg_queue.text, buffer);
//向消息队列发消息
if(msgsnd(msgid, (void*)&msg_queue, MAX_LENGTH, 0) == -1)
{
fprintf(stderr, "msgsnd failed with error code : %d\n", errno);
exit(EXIT_FAILURE);
}
if(strncmp(buffer, "end", 3) == 0)
running = 0;//stop
//sleep(1);
}
exit(EXIT_SUCCESS);
}
(四)、接收消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数声明:
- msqid:消息队列的标识码。
- *msgp:指向消息缓冲区的指针。
- msgsz:消息的长短。
- msgtyp:消息类型。等于0时,则返回队列的最早的一个消息;大于0时,则返回其类型为mtype的第一个消息;小于0时,则返回其类型小于或等于mtype参数的绝对值的最小的一个消息。
- msgflg:标志位,当它为0时,则表示忽略。
返回值:
- 成功则返回数据长度,错误则返回-1。
消息缓冲区的结构如下:
struct msgbuf {
long mtype; /* message type, must be > 0 消息类型,必须大于0 */
char mtext[msgsz]; /* message data 存放消息的数据 */
};
示例:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#define MAX_LENGTH 512
struct msg_st
{
long int msg_type;
char text[MAX_LENGTH];
};
int main(int argc, char* argv[])
{
int running = 1;
struct msg_st msg_queue;
int msgid = -1;
long int msgtype = 0; //注意1
//创建消息队列
msgid = msgget((key_t)1234, IPC_CREAT |0666);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error code : %d\n", errno);
exit(EXIT_FAILURE);
}
//从消息队列获取消息
while(running)
{
if(msgrcv(msgid, (void*)&msg_queue, MAX_LENGTH, msgtype, 0) == -1)
{
fprintf(stderr, "msgrcv failed with error code : %d\n", errno);
exit(EXIT_FAILURE);
}
if(strncmp(msg_queue.text, "end", 3) == 0)
running = 0;//stop
printf("receive message : %s\n", msg_queue.text);
}
//删除消息队列
if(msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
return 0;
}
先将消息队列接收任务放到后台运行,
sudo ./process_msg_receive &
然后启动消息队列发送任务。
./process_msg_send
七、信号
#include <signal.h>
typedef void (*sighandler_t)(int);
这条类型定义语句表示的是定义一个类型sighandler_t,它表示指向返回void类型的参数为int类型的指针。
sighandler_t signal(int signum, sighandler_t handler);
参数说明:
- signum:事件编号。
常见的事件:
SIGALRM:闹钟事件(alarm()函数等)
SIGHUP:终端发出的结束信号
SIGINT:键盘键入ctrl+c
SIGKILL:kill命令产生的信号
SIGSTOP:键盘键入ctrl+z
- handler:事件句柄,即触发事件之后要执行的函数。注意函数的类型要是sighandler_t。
返回值:成功则返回当前触发的信号句柄,错误则返回SIG_ERR。
按下按键、发生硬件异常、使用kill函数或者命令都会产生信号。
(一)、信号集
#include <signal.h>
int sigemptyset(sigset_t *set);
函数说明:初始化set为empty,所有信号都被排除。
int sigfillset(sigset_t *set);
函数说明:初始化set为full,包括所有信号。
int sigaddset(sigset_t *set, int signum);
函数说明:从set中添加信号signum。
int sigdelset(sigset_t *set, int signum);
函数说明::从set中删除信号signum。
int sigismember(const sigset_t *set, int signum);
函数说明::测试signum是否为set中的成员。返回1则说明signum是set中的成员,返回0则不是。
信号集的结构体如下:
struct sigaction {
void (*sa_handler)(int);//指向事件句柄
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;//信号屏蔽
int sa_flags;//信号标志位
void (*sa_restorer)(void);
};
示例:
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
void handler(int sig)
{
printf("\nsignal trigged %d\n", sig);
}
int main(int argc, char* argv[])
{
sigset_t sigset;//用于记录屏蔽字的信号量 mask
sigset_t ign;//用于记录被阻塞的信号集 blocked
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 within 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);
}
程序开始等待信号ctrl+c退出阻塞,然后屏蔽信号ctrl+c。
- 若在10s内输入都无输入信号ctrl+c,则之后由于解除屏蔽信号,程序阻塞并等待信号,当输入信号后程序在5s后退出;
- 若10s内输入信号ctrl+c,依然要等待10s然后程序检测发现信号ctrl+c是未决信号从而输出信号被忽略的信息。然后程序被挂起,但由于已经输入了信号(此时信号是未决信号,即未被处理的信号,解除屏蔽之后他就可以被处理了),事件被触发,程序退出阻塞,之后等待5s退出程序。
八、信号量
信号量是用来调协进程对共享资源的访问。为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行,而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
(一)、信号量创建函数
int semget(key_t key, int nsems, int semflg);
参数说明:
- key:信号量集的键值,不同进程通过它来进行访问。
不相关的进程可以通过它访问一个信号量,只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。
- nsems:指定需要的信号量数目,它的值几乎总是1。
- semflg:一组标志,权限+创建。
返回值:
- 成功则返回有效的信号量集标识符(semaphore set identifier)semid,错误则返回-1。
(二)、信号量控制函数
int semctl(int semid, int semnum, int cmd, …);
参数说明:
- semid:信号量集标识符,由semget返回获得。
- semnum:指定信号量集中的某个信号量,从0开始计算。
- cmd:函数指令(有IPC_STAT、IPC_SET、IPC_RMID[将信号量集从内存中删除]、SETVAL[设置信号量集中的一个单独的信号量的值]等等),它的选择会决定函数有三个还是四个参数,当有四个参数时,第四个参数为联合体semun。调用程序时必须如下定义这个union:
联合体 semun的成员如下所示。具体使用哪一个成员根据具体的cmd选择。
//指向内核中使用的数据结构的指针
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
//结构体 semid_ds的成员如下所示。
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions 所有权和权限*/
time_t sem_otime; /* Last semop time 上一次执行semop的时间*/
time_t sem_ctime; /* Last change time 上一次改变结构体的时间*/
unsigned short sem_nsems; /* No. of semaphores in set 信号量在集合中的编号*/
};
返回值:
- 返回-1则发生错误,函数执行正确的返回值需要根据cmd来确定。
(三)、信号量操作函数
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数说明:
- semid:信号量集标识符,由semget返回获得。
- *sops:指向存储信号操作结构sembuf的数组指针,需要根据要求进行修改。
- nsops:信号操作结构的数量,恒大于或等于1。
返回值:
- 成功则返回0,错误则返回-1。
结构体sembuf的成员如下所示。
struct sembuf{
unsigned short sem_num; /* semaphore number 指定信号量集中的某个信号量,从0开始计算(除非使用一组信号量,否则它为0)。*/
short sem_op; /* semaphore operation 信号量操作(操作时需要改变的数据,-1即P等待,+1即V发送信号)*/
short sem_flg; /* operation flags 信号量标志位,通常为SEM_UNDO,使操作系统跟踪信号*/
}
示例:
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/stat.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
// struct seminfo *__buf; /* Buffer for IPC_INFO
// (Linux-specific) */
};
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);//休眠2s
}
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);
return 0;
}
static int set_semvalue(void)
{
//初始化信号量
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(void)
{
//删除信号量
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(void)
{
//对信号量进行减1操作,即等待信号
struct sembuf sem_b;
sem_b.sem_num = 0;//操作第0个信号量
sem_b.sem_op = -1;//等待信号
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(void)
{
//对信号量进行加1操作,释放信号,使信号量变为可用,即发送信号
struct sembuf sem_b;
sem_b.sem_num = 0;//操作第0个信号量
sem_b.sem_op = 1;//发送信号
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1)
{
fprintf(stderr, "semaphore_v failed\n");
return 0;
}
return 1;
}
输入./process_sem a&./process_sem
会发现调试窗口输出如下信息(输出的信息不唯一,因为sleep时间是随机的,但信息都是成对输出X.X a.a)。
X.XX.Xa.aX.Xa.aX.Xa.aX.XX.Xa.aX.Xa.aX.Xa.aX.XX.Xa.aa.aa.aa.a
九、共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
(一)、共享内存创建函数
int shmget(key_t key, size_t size, int shmflg);
参数说明:
- key:共享内存的键值,不同进程通过它来进行访问。
- size:共享内存的大小。
- shmflg:一组标志,权限+创建。
返回值:
- 成功则返回有效的段标识符(segment identifier)shmid,错误则返回-1。
(二)、共享内存地址获取函数
1、连接共享内存地址(分配内存)
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数说明:
- shmid:段标识符(共享内存标识符),由shmget返回获得。
- shmaddr:关联的共享内存段地址,若设为NULL则让内核选择合适的位置。
- shmflg:SHM_RDONLY为只读模式,SHM_RND为可读可写模式。
返回值:
- 正确则返回共享内存的起始地址,错误则返回(void *) -1。
2、分离共享内存地址(释放内存)
int shmdt(const void *shmaddr);
参数说明:
- shmaddr:共享内存的起始地址(从段地址开始的偏移值)。
返回值:
- 正确则返回0,错误则返回-1。
(三)、共享内存控制函数
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数说明:
- shmid:段标识符(共享内存标识符),由shmget返回获得。
- cmd:函数指令(有IPC_STAT、IPC_SET、IPC_RMID[删除共享内存]等等)。
- *buf:指向共享内存管理结构体的指针。
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions 所有权和权限*/
size_t shm_segsz; /* Size of segment (bytes) 段的大小*/
time_t shm_atime; /* Last attach time 上次附加的时间*/
time_t shm_dtime; /* Last detach time 上次分离的时间*/
time_t shm_ctime; /* Last change time 上次改变的时间*/
pid_t shm_cpid; /* PID of creator 创建者的pid*/
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) 上一个使用函数shmat(2)/shmdt(2)的进程pid */
shmatt_t shm_nattch; /* No. of current attaches 当前连接的编号*/
...
};
返回值:
- 返回-1则发生错误,函数执行正确的返回值需要根据cmd来确定。
示例:
process_shmdata.h
#ifndef __PROCESS_SHMDATA_H
#define __PROCESS_SHMDATA_H
#define TEXT_SZ 2048
struct shared_use_st
{
//读写标志位 非0:可读 0:可写
int written;
//记录写入和读取的文本
char text[TEXT_SZ];
};
#endif
process_shmwrite.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "process_shmdata.h"
int main(int argc, char* argv[])
{
//用于结束程序
int running = 1;
void *shm = NULL;
struct shared_use_st *shared = NULL;
//用于保存输入的文本
char buffer[BUFSIZ + 1];
//共享内存id
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);
}
process_shmread.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "process_shmdata.h"
int main(int argc, char* argv[])
{
//用于结束程序
int running = 1;
void *shm = NULL;
struct shared_use_st *shared;
//共享内存id
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;
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);
}
运行./process_shmread &
将读取共享内存进行后台运行;然后运行./process_shmwrite
开启共享内存写进程,会提示请求输入的信息,输入信息后写进程会将数据保存在共享内存,之后读取进程会根据已改变的标志位输出保存在共享内存的信息。最后输入’end’结束读写进程。