1. 进程概述
在UNIX和类UNIX系统中,进程是执行程序的基本单位。每个进程都有一个独立的地址空间,有自己的程序代码、数据和系统资源。进程可以创建其他进程(子进程),也可以与其他进程(父进程)终止关联。
2. 进程控制
在UNIX环境下,有多种方式可以用来控制和管理进程,如:创建、终止、等待、查看状态等。
2.1 创建进程
在UNIX系统中,创建新进程的唯一方法是使用fork()函数。fork()函数会创建一个新的进程,这个新的进程是原进程的副本。新的进程(子进程)获得与原进程(父进程)几乎相同的地址空间。
以下是一个简单的fork()函数示例:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- int main() {
- pid_t pid;
- pid = fork(); //创建一个新进程
- if (pid < 0) { //如果fork失败
- fprintf(stderr, "Fork failed.\n");
- exit(-1);
- } else if (pid == 0) { //子进程
- printf("I am the child, my pid is %d and my parent's pid is %d.\n", getpid(), getppid());
- } else { //父进程
- printf("I am the parent, my pid is %d and my child's pid is %d.\n", getpid(), pid);
- }
- return 0;
- }
复制代码 2.2 终止进程
在UNIX系统中,有多种方法可以用来终止进程,如:exit()、_exit()、kill()等。exit()和_exit()函数用于自愿终止进程,而kill()函数用于非自愿终止进程。
以下是一个简单的kill()函数示例:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <signal.h>
-
- int main() {
- pid_t pid;
- pid = fork(); //创建一个新进程
- if (pid < 0) { //如果fork失败
- fprintf(stderr, "Fork failed.\n");
- exit(-1);
- } else if (pid == 0) { //子进程
- printf("I am the child, my pid is %d and my parent's pid is %d.\n", getpid(), getppid());
- kill(getppid(), SIGKILL); //杀死父进程
- } else { //父进程
- printf("I am the parent, my pid is %d and my child's pid is %d.\n", getpid(), pid);
- }
- return 0;
- }
复制代码 2.3 等待进程
在UNIX系统中,父进程可以使用wait()或waitpid()函数来等待子进程的终止。这种等待让父进程可以获取子进程的退出状态,同时也可以让父进程避免产生僵尸进程。
以下是一个简单的waitpid()函数示例:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/wait.h>
- #include <signal.h>
-
- int main() {
- pid_t pid;
- int status;
- pid = fork(); //创建一个新进程
- if (pid < 0) { //如果fork失败
- fprintf(stderr, "Fork failed.\n");
- exit(-1);
- } else if (pid == 0) { //子进程
- printf("I am the child, my pid is %d and my parent's pid is %d.\n", getpid(), getppid());
- kill(getppid(), SIGKILL); //杀死父进程
- } else { //父进程
- printf("I am the parent, my pid is %d and my child's pid is %d.\n", getpid(), pid);
- waitpid(pid, &status, 0); //等待子进程结束并获取退出状态
- printf("Child exited with status %d.\n", status); //输出子进程的退出状态
- }
- return 0;
- }
复制代码 2.4 查看进程状态
在UNIX环境下,我们可以使用ps命令或者getprocs函数来查看进程的状态。
2.4.1 使用ps命令
ps命令可以用来查看系统中当前运行的进程状态。例如,可以使用以下命令查看所有进程:
这将显示所有用户的所有进程的详细信息,包括进程ID、CPU使用率、内存使用情况等。
2.4.2 使用getprocs函数
在C语言中,我们可以使用getprocs函数来获取系统中的进程信息。getprocs函数会返回一个包含所有进程的结构体数组。
以下是一个简单的示例程序,演示如何使用getprocs函数:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/sysctl.h>
- #include <structproc.h>
-
- int main() {
- int numprocs;
- struct proc_struct *procs;
- numprocs = sysctl(1, 0, 0); //获取系统中的进程数
- procs = malloc(numprocs * sizeof(struct proc_struct)); //分配内存空间
- getprocs(procs, numprocs, 0, 0); //获取进程信息
- for(int i = 0; i < numprocs; i++) {
- printf("Process %d: %s\n", procs[i].p_pid, procs[i].p_comm); //输出进程ID和进程名称
- }
- free(procs); //释放内存空间
- return 0;
- }
复制代码 3. 进阶进程管理
除了上述基本的进程管理方法,UNIX和类UNIX系统还提供了一些更高级的进程管理功能,如线程创建和管理、进程组、会话、信号处理等。这些功能可以极大地满足复杂应用程序的进程管理需求。
3.1 线程创建和管理在UNIX和类UNIX系统中,线程是进程的基本执行单元。一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间、打开的文件和网络连接等。
要创建一个线程,可以使用pthread_create函数。该函数接受一个指向线程属性结构体的指针、一个指向线程函数的指针和一个指向传递给线程函数的参数的指针。例如:
- #include <pthread.h>
-
- void *my_thread_func(void *arg) {
- // 线程函数的代码
- return NULL;
- }
-
- int main() {
- pthread_t my_thread;
- int ret = pthread_create(&my_thread, NULL, my_thread_func, NULL);
- if (ret != 0) {
- // 线程创建失败的处理代码
- return 1;
- }
- // 等待线程结束的代码
- pthread_join(my_thread, NULL);
- return 0;
- }
复制代码 3.2 进程组进程组是一组相关进程的集合。在UNIX和类UNIX系统中,进程组由一个进程组ID(PGID)标识。进程可以通过调用setpgid函数将自己加入到一个已有的进程组中,或者创建一个新的进程组。进程组的创建者称为进程组组长(PGID为0),可以调用setpgid函数将自己设置为进程组组长。进程组组长可以调用setpgid函数将自己的PGID传递给其他进程,使其成为新的进程组组长。
进程组组长可以调用killpg函数向整个进程组发送信号。例如,要向一个进程组发送一个SIGTERM信号,可以使用以下代码:
- #include <signal.h>
- #include <unistd.h>
- #include <stdio.h>
-
- int main() {
- pid_t pgid = getpgid(0); // 获取当前进程的PGID
- killpg(pgid, SIGTERM); // 向整个进程组发送SIGTERM信号
- printf("Sent SIGTERM to process group %d\n", pgid);
- return 0;
- }<b>
- </b>
复制代码 3.3 会话会话是一组相关进程的集合,由一个会话ID(SID)标识。在UNIX和类UNIX系统中,会话的创建者称为会话领导(SID为0),可以调用setsid函数将自己设置为会话领导。会话领导可以调用setsid函数将自己的SID传递给其他进程,使其成为新的会话领导。会话领导可以调用killpg函数向整个会话发送信号。
3.4 信号处理
信号是UNIX和类UNIX系统用于进程间通信的一种机制。信号是一段二进制数据,可以携带有关事件的信息(例如,进程终止、挂起、就绪等)。进程可以接收、处理和发送信号。
在UNIX和类UNIX系统中,有三种类型的信号:
- 实时信号(可被捕获并处理):例如,SIGINT(对应键盘中断)和SIGTERM(对应进程终止)。
- 不可靠信号(可能会被忽略或丢失):例如,SIGSTOP(对应停止进程)和SIGKILL(对应终止进程)。
- 可靠信号:一种进程可以请求系统在某个未来时间点发送的信号。
处理信号的常见方法包括:
- 使用signal函数注册信号处理程序:signal(int signum, void (*func)(int))。当signum指定的信号发生时,系统将调用func函数。
- 使用raise函数发送信号给当前进程:raise(int signum)。
- 使用kill函数发送信号给指定进程:kill(pid_t pid, int signum)。
- 使用waitpid、wait或类似的函数等待子进程终止,并获取其状态信息。
注意,在使用信号时需要注意以下问题:
- 同一进程只能对每个信号有一个处理程序。
- 在多线程环境中,需要注意信号处理函数的线程安全性。
- 在信号处理函数中调用某些库函数可能会不安全,因为这些函数可能不是异步信号安全的。
- 信号的发送和接收可能会受到进程的实际用户和组ID的限制。
以上就是UNIX和类UNIX系统的进程管理高级功能。这些功能可以使复杂的应用程序更好地管理其进程资源。
4. 进程间通信
在UNIX环境中,有多种方式可以让进程之间进行数据交换,这些方式被称为进程间通信(IPC)。下面介绍几种常见的IPC方式:
4.1 管道
管道(pipe)是一种最简单的进程间通信方式,它可以让一个进程的输出直接成为另一个进程的输入。管道通常用于父子进程之间的通信。
以下是一个使用管道的简单示例程序:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- int main() {
- int pipefd[2];
- pid_t pid;
- char buf[1024];
-
- if (pipe(pipefd) == -1) {
- perror("pipe");
- exit(-1);
- }
-
- pid = fork();
- if (pid == -1) {
- perror("fork");
- exit(-1);
- } else if (pid == 0) { // 子进程
- close(pipefd[1]); // 关闭管道的写端
- read(pipefd[0], buf, sizeof(buf));
- printf("Child process received message: %s\n", buf);
- close(pipefd[0]);
- } else { // 父进程
- close(pipefd[0]); // 关闭管道的读端
- write(pipefd[1], "Hello from parent", 17);
- close(pipefd[1]);
- }
- return 0;
- }
复制代码 要管理线程,可以使用pthread_join函数等待线程结束,使用pthread_exit函数从线程中退出,使用pthread_cancel函数取消线程等。这些函数的用法与上述函数类似。
4.2 命名管道
命名管道(named pipe)是管道的一种扩展,它允许无亲缘关系进程间的通信。命名管道通常在文件系统中以特殊文件形式存在,类似于普通文件。
4.3 信号
信号(signal)是一种可以由一个进程发送给另一个进程的软件中断,用于通知对方某个事件已经发生或者请求响应。例如,当用户按下Ctrl+C时,系统会向当前前景进程发送SIGINT信号。
以下是一个使用信号的简单示例程序:
- #include <stdio.h>
- #include <stdlib.h>
- #include <signal.h>
- #include <unistd.h>
-
- void signal_handler(int signum) {
- printf("Received signal %d\n", signum);
- }
-
- int main() {
- signal(SIGINT, signal_handler); // 注册信号处理函数
- while(1) {
- printf("Waiting for signal...\n");
- sleep(1);
- }
- return 0;
- }
复制代码 4.4 共享内存
共享内存是一种高效的进程间通信方式,它允许不同进程访问同一块物理内存空间。共享内存允许多个进程之间快速交换数据,并且可以避免复制数据的开销。
以下是一个使用共享内存的简单示例程序:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ipc.h>
- #include <sys/shm.h>
-
- #define SHM_SIZE 1024
-
- int main() {
- int shmid;
- key_t key;
- char *shm, *s;
- pid_t pid;
-
- key = 9876;
- shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
- if (shmid < 0) {
- perror("shmget");
- exit(-1);
- }
-
- shm = shmat(shmid, NULL, 0);
- if (shm == (char *) -1) {
- perror("shmat");
- exit(-1);
- }
-
- // 在共享内存中写入数据
- s = shm;
- for (char c = 'a'; c <= 'z'; c++) {
- *s++ = c;
- }
- *s = '\0';
-
- // 创建子进程,并等待子进程结束
- pid = fork();
- if (pid == -1) {
- perror("fork");
- exit(-1);
- } else if (pid == 0) { // 子进程
- // 在共享内存中读取数据
- printf("Child process read message: %s\n", shm);
- exit(0);
- } else { // 父进程
- wait(NULL);
- printf("Parent process read message: %s\n", shm);
- }
-
- // 分离共享内存并删除共享内存标识符
- shmdt(shm);
- shmctl(shmid, IPC_RMID, NULL);
- return 0;
- }
复制代码 4.5 消息队列
消息队列是一种高级的进程间通信方式,它允许不同进程之间以消息的形式进行数据交换。消息队列具有优先级、容量限制和持久性等特性。
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ipc.h>
- #include <sys/msg.h>
- #include <string.h>
-
- #define MSG_SIZE 1024
- #define QUEUE_SIZE 1000000 // 设置队列容量为10M,可根据实际情况调整容量大小。
-
- // 消息结构体
- struct msg_buffer {
- long mtype;
- char mtext[MSG_SIZE];
- };
-
- int main() {
- key_t key;
- int msg_id;
- struct msg_buffer send_msg, recv_msg;
-
- // 生成唯一的键,用于创建或打开消息队列
- if ((key = ftok("/tmp", 'R')) == -1) {
- perror("ftok");
- exit(1);
- }
-
- // 创建或打开消息队列
- if ((msg_id = msgget(key, IPC_CREAT | 0666)) == -1) {
- perror("msgget");
- exit(1);
- }
-
- // 发送消息
- send_msg.mtype = 1; // 消息类型
- strcpy(send_msg.mtext, "Hello from sender!"); // 消息内容
- if (msgsnd(msg_id, &send_msg, MSG_SIZE, 0) == -1) {
- perror("msgsnd");
- exit(1);
- }
- printf("Message sent: %s\n", send_msg.mtext);
-
- // 接收消息
- if (msgrcv(msg_id, &recv_msg, MSG_SIZE, 1, 0) == -1) {
- perror("msgrcv");
- exit(1);
- }
- printf("Message received: %s\n", recv_msg.mtext);
-
- // 删除消息队列
- if (msgctl(msg_id, IPC_RMID, 0) == -1) {
- perror("msgctl");
- exit(1);
- }
- return 0;
- }
复制代码 4.6 信号量
信号量是一种同步机制,用于控制多个进程对共享资源的访问。信号量可以是二进制信号量或计数信号量。
以下是一个使用二进制信号量的简单示例程序:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ipc.h>
- #include <sys/sem.h>
-
- #define SEM_SIZE 1000 // 设置信号量大小为1000,可根据实际情况调整容量大小。
-
- int main() {
- int semid;
- key_t key;
- struct sembuf sops;
- int semval;
-
- key = 9876;
- semid = semget(key, 1, IPC_CREAT | 0666); // 创建信号量标识符
- if (semid < 0) {
- perror("semget");
- exit(-1);
- }
-
- sops.sem_num = 0; // 操作号
- sops.sem_op = -1; // 减操作,减少一个信号量
- sops.sem_flg = SEM_UNDO; // 自动释放信号量
- semval = semctl(semid, 0, GETVAL); // 获取信号量初始值
- if (semval < 0) {
- perror("semctl");
- exit(-1);
- }
-
- printf("Semaphore initial value = %d\n", semval); // 输出信号量初始值
-
- sops.sem_op = 1; // 加操作,增加一个信号量
- sops.sem_flg = SEM_UNDO; // 自动释放信号量
- semctl(semid, 0, SETVAL, semval + 1); // 设置信号量值为初始值+1
- if (semctl(semid, 0, GETVAL) != semval + 1) { // 获取更新后的信号量值,验证操作是否成功
- perror("semctl");
- exit(-1);
- }
-
- printf("Semaphore updated value = %d\n", semctl(semid, 0, GETVAL)); // 输出更新后的信号量
- }
复制代码 4.6.1 计数信号量
计数信号量是一种特殊的信号量,它用于限制访问共享资源的最大并发进程数。计数信号量的值表示当前允许访问共享资源的进程数。当一个进程访问共享资源时,计数信号量的值减1;当一个进程释放共享资源时,计数信号量的值加1。
以下是一个使用计数信号量的简单示例程序:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ipc.h>
- #include <sys/sem.h>
-
- #define SEM_SIZE 10 // 设置信号量大小为10,可根据实际情况调整容量大小。
-
- int main() {
- int semid;
- key_t key;
- struct sembuf sops;
- int semval;
-
- key = 9876;
- semid = semget(key, SEM_SIZE, IPC_CREAT | 0666); // 创建信号量标识符
- if (semid < 0) {
- perror("semget");
- exit(-1);
- }
-
- for (int i = 0; i < SEM_SIZE; i++) { // 初始化信号量值为SEM_SIZE
- sops.sem_num = i; // 操作号
- sops.sem_op = 1; // 加操作,增加一个信号量
- sops.sem_flg = SEM_UNDO; // 自动释放信号量
- semctl(semid, i, SETVAL, SEM_SIZE - i); // 设置信号量值为SEM_SIZE - i
- }
-
- for (int i = 0; i < SEM_SIZE; i++) { // 循环减少信号量值,表示当前允许访问的进程数
- sops.sem_num = i; // 操作号
- sops.sem_op = -1; // 减操作,减少一个信号量
- sops.sem_flg = SEM_UNDO; // 自动释放信号量
- semctl(semid, i, SEM_UNDO, sops); // 执行减操作
- }
- }
复制代码 4.6.2 进程组和会话
进程组和会话是UNIX系统中进程的两种重要组织形式。进程组是对一组进程的抽象,它们共享相同的PID(进程ID)。会话则是一组进程组的集合,这些进程组共享相同的会话领导(会话组长)。会话组长拥有对会话的控制权,如挂起、恢复等。
以下是一个使用进程组和会话的简单示例程序:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/ipc.h>
- #include <sys/sem.h>
-
- #define SEM_SIZE 10 // 设置信号量大小为10,可根据实际情况调整容量大小。
-
- int main() {
- int semid, pid, pgid;
- key_t key;
- struct sembuf sops;
- int semval;
-
- key = 9876;
- semid = semget(key, SEM_SIZE, IPC_CREAT | 0666); // 创建信号量标识符
- if (semid < 0) {
- perror("semget");
- exit(-1);
- }
-
- pid = getpid(); // 获取当前进程的PID
- pgid = getpgrp(); // 获取当前进程的进程组ID
-
- printf("Current process ID: %d\n", pid); // 输出当前进程的PID
- printf("Current process group ID: %d\n", pgid); // 输出当前进程的进程组ID
- }
复制代码 4.6.3 共享内存
共享内存允许多个进程访问同一块物理内存空间,从而避免了数据的复制,因此效率很高。多个进程可以通过共享内存来共享数据,通常用于实现多进程之间的信息共享、协调同步等。
以下是一个使用共享内存的简单示例程序:
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/ipc.h>
- #include <sys/shm.h>
-
- #define SHM_SIZE 1024 // 设置共享内存大小为1024字节,可根据实际情况调整容量大小。
-
- int main() {
- int shmid;
- key_t key;
- char *shm, *s;
- pid_t pid;
-
- key = 9876;
- shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666); // 创建共享内存标识符
- if (shmid < 0) {
- perror("shmget");
- exit(-1);
- }
-
- shm = shmat(shmid, NULL, 0); // 附加到共享内存
- if (shm == (char *) -1) {
- perror("shmat");
- exit(-1);
- }
-
- // 在共享内存中写入数据
- s = shm;
- for (char c = 'a'; c <= 'z'; c++) {
- *s++ = c;
- }
- *s = '\0';
-
- // 创建子进程,并等待子进程结束
- pid = fork();
- if (pid == -1) {
- perror("fork");
- exit(-1);
- } else if (pid == 0) { // 子进程
- // 在共享内存中读取数据
- printf("Child process read message: %s\n", shm);
- exit(0);
- } else { // 父进程
- wait(NULL); // 等待子进程结束
- printf("Parent process read message: %s\n", shm);
- }
-
- // 分离共享内存并删除共享内存标识符
- shmdt(shm);
- shmctl(shmid, IPC_RMID, NULL);
- return 0;
- }
复制代码 4.6.4 信号
信号是一种软件中断,用于通知进程发生了某个事件。进程可以通过捕获信号、执行相应的处理程序(信号处理函数)来响应信号。常见的信号包括 SIGINT(中断信号)、SIGTERM(终止信号)等。
以下是一个使用信号的简单示例程序:
- #include <stdio.h>
- #include <stdlib.h>
- #include <signal.h>
- #include <unistd.h>
-
- #define SIG_NUM 1
-
- void handle_signal(int signo) {
- printf("Received signal %d\n", signo);
- exit(0);
- }
-
- int main() {
- struct sigaction sa;
- sa.sa_handler = handle_signal;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = 0;
- if (sigaction(SIG_NUM, &sa, NULL) == -1) {
- perror("sigaction");
- exit(-1);
- }
- printf("Sending signal to self\n");
- kill(getpid(), SIG_NUM);
- return 0;
- }
复制代码 在这个示例程序中,我们首先定义了一个 handle_signal 函数,用于处理收到的信号。接着,在 main 函数中,我们使用 sigaction 函数注册了一个信号处理程序(即 handle_signal 函数),并使用 kill 函数向自身发送一个信号(即 SIG_NUM 信号)。当接收到该信号时,就会执行 handle_signal 函数,并输出一条消息后退出程序。
4.6.5 文件锁
文件锁是一种机制,用于在并发访问文件时防止数据竞争或不一致状态。文件锁允许进程或线程对文件进行加锁,以实现对文件的独占访问或同步访问。
以下是一个使用文件锁的简单示例程序:
- #include <stdio.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <sys/file.h>
-
- #define FILENAME "/tmp/lock_file"
-
- int main() {
- int fd;
- fd = open(FILENAME, O_WRONLY | O_CREAT, 0666); // 打开或创建文件
- if (fd == -1) {
- perror("open");
- exit(-1);
- }
- if (flock(fd, LOCK_EX) == -1) { // 对文件加独占锁
- perror("flock");
- exit(-1);
- }
- printf("Acquired lock on file %s\n", FILENAME);
- sleep(5); // 模拟进程休眠时间
- printf("releasing lock\n");
- if (flock(fd, LOCK_UN) == -1) { // 释放锁
- perror("flock");
- exit(-1);
- }
- close(fd); // 关闭文件
- return 0;
- }
复制代码 在这个示例程序中,我们使用了 flock 函数对文件进行加锁和解锁操作。首先,我们打开或创建了一个名为 /tmp/lock_file 的文件,并使用 flock 函数对该文件加上了独占锁。然后,模拟进程休眠一段时间(以便其他进程有机会尝试对文件加锁),最后释放文件锁,并关闭文件。
|