2.linux系统编程之进程
linux系统编程之进程
1. 进程基础概念
进程是Linux系统中最基本的执行单元,它是程序的一次执行实例。每个进程都有自己的地址空间、文件描述符、环境变量和程序计数器等资源。
进程特点:
- 独立性:每个进程有独立的地址空间
- 动态性:进程有创建、运行、终止等生命周期
- 并发性:多个进程可以并发执行
- 结构性:进程由PCB(进程控制块)描述
2. 进程创建
fork()系统调用
#include <unistd.h>
pid_t fork(void);
- 功能:创建子进程
- 返回值:
- 父进程中返回子进程的PID
- 子进程中返回0
- 出错返回-1
示例代码:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
printf("This is child process (PID: %d)\n", getpid());
} else {
printf("This is parent process (PID: %d), child PID: %d\n",
getpid(), pid);
}
return 0;
}
vfork()系统调用
#include <unistd.h>
pid_t vfork(void);
- 与fork()类似,但子进程与父进程共享地址空间
- 子进程必须先调用exec()或_exit(),否则会导致未定义行为
- 通常用于创建后立即执行exec的场景
3. 进程终止
正常终止方式:
- 从main函数返回
- 调用exit()
- 调用_exit()或_Exit()
- 最后一个线程从其启动例程返回
- 最后一个线程调用pthread_exit()
相关函数:
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
区别:
- exit()会执行清理操作(如调用atexit注册的函数、刷新缓冲区等)
- _exit()直接终止进程,不做清理
4. 进程等待
wait()系统调用
#include <sys/wait.h>
pid_t wait(int *status);
- 功能:等待任意子进程终止
- 参数:status保存子进程退出状态
- 返回值:成功返回终止子进程的PID,失败返回-1
waitpid()系统调用
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
-
功能:等待指定子进程终止
-
参数:
-
pid:指定等待的进程ID
-
0:等待特定子进程
-
-1:等待任意子进程(同wait)
-
0:等待与调用进程同组ID的任何子进程
-
<-1:等待组ID等于pid绝对值的任何子进程
-
-
options:选项
- WNOHANG:非阻塞模式
- WUNTRACED:报告已停止的子进程
-
-
返回值:同wait()
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
printf("Child process (PID: %d) running\n", getpid());
sleep(2);
printf("Child process exiting\n");
exit(123);
} else {
printf("Parent process (PID: %d) waiting for child\n", getpid());
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("Child exited with status: %d\n", WEXITSTATUS(status));
} else {
printf("Child terminated abnormally\n");
}
}
return 0;
}
5. 进程替换
exec函数族
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, 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[]);
- 功能:用新程序替换当前进程映像
- 命名规律:
- l:参数列表形式传递
- v:数组形式传递
- p:使用PATH环境变量查找可执行文件
- e:自定义环境变量
示例代码:
#include <stdio.h>
#include <unistd.h>
int main() {
printf("Before exec\n");
// 替换为ls命令
execlp("ls", "ls", "-l", NULL);
// 只有exec失败才会执行到这里
perror("exec failed");
return 1;
}
6. 进程间通信(IPC)
主要IPC机制:
- 管道(pipe)
- 命名管道(FIFO)
- 消息队列
- 共享内存
- 信号量
- 信号
- 套接字(socket)
6.1. 管道(Pipe)
无名管道示例
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc, char const *argv[])
{
/* code */
int fd[2];
if(pipe(fd) == -1){
perror("pipe failed");
return -1;
}
pid_t pid = fork();
if(pid < 0){
perror("fork failed");
return -1;
}else if(pid == 0){
//关闭写端
close(fd[1]);
char buf[100];
int n = read(fd[0],buf,sizeof(buf));
printf("Child received: %.*s \n",n,buf);
close(fd[0]);
}else{
//关闭读端
close(fd[0]);
const char *msg = "Hello from parent";
write(fd[1],msg, strlen(msg));
close(fd[1]);
wait(NULL);
}
return 0;
}
命名管道(FIFO)示例
写入端:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(int argc, char const *argv[])
{
/* code */
const char *fifo = "/tmp/myfifo";
//创建FIFO
mkfifo(fifo,0666);
//打开FIFO
int fd = open(fifo,O_WRONLY);
if(fd == -1){
perror("fifo open failed");
return -1;
}
write(fd,"Hello FIFO",10);
close(fd);
return 0;
}
读取端:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
/* code */
const char *fifo = "/tmp/myfifo";
char buf[256];
int fd = open(fifo,O_RDONLY);
if(fd == -1){
perror("fifo open failed");
return -1;
}
int size = read(fd, buf, sizeof(buf));
printf("FIFO Read : %.*s \n", size, buf);
close(fd);
return 0;
}
2. 消息队列(Message Queue)
#include <stdio.h>
#include <sys/msg.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
struct msgbuf
{
long mtype;
char mtext[100];
};
int main(int argc, char const *argv[])
{
/* code */
int msgid;
struct msgbuf msg;
msgid = msgget(IPC_PRIVATE, 0666 |IPC_CREAT);
pid_t pid = fork();
if(pid < 0){
perror("fork failed");
return -1;
}else if( pid == 0){
msg.mtype = 1;
strcpy(msg.mtext,"hello my parent, i pass Message Queue");
msgsnd(msgid,&msg,sizeof(msg),0);
}else {
msgrcv(msgid,&msg,sizeof(msg),1,0);
printf("parent rev data: %s \n",msg.mtext);
//删除消息队列
msgctl(msgid,IPC_RMID,NULL);
wait(NULL);
}
return 0;
}
3. 共享内存(Shared Memory)
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
/* code */
int shmid;
char *shm;
//创建共享内存
shmid = shmget(IPC_PRIVATE, 1024, 0666| IPC_CREAT);
shm = shmat(shmid, NULL, 0);
pid_t pid = fork();
if(pid < 0){
perror("fork failed");
return -1;
}else if(pid == 0){
strcpy(shm,"hello my share memory parent");
}else {
wait(NULL);
printf("Parent read: %s \n",shm);
shmdt(shm);
shmctl(shmid , IPC_RMID , NULL);
}
return 0;
}
4. 信号量(Semaphore)
#include <stdio.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
// 联合体用于semctl
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main(int argc, char const *argv[])
{
int semid;
union semun arg;
struct sembuf sb = {0, -1, 0};//P操作
//创建信号量
semid = semget(IPC_PRIVATE,1, 0666|IPC_CREAT);
arg.val= 1;//初始值为1
semctl(semid,0,SETVAL,arg);
pid_t pid = fork();
if(pid < 0){
perror("fork failed");
return -1;
}else if (pid == 0) { // 子进程
printf("Child waiting for semaphore...\n");
semop(semid, &sb, 1); // P操作
printf("Child entered critical section\n");
sleep(2);
printf("Child leaving critical section\n");
sb.sem_op = 1; // V操作
semop(semid, &sb, 1);
} else { // 父进程
sleep(1); // 让子进程先运行
printf("Parent waiting for semaphore...\n");
semop(semid, &sb, 1); // P操作
printf("Parent entered critical section\n");
sleep(2);
printf("Parent leaving critical section\n");
sb.sem_op = 1; // V操作
semop(semid, &sb, 1);
wait(NULL);
semctl(semid, 0, IPC_RMID); // 删除信号量
}
return 0;
}
5. 套接字(Socket)
本地套接字示例
服务器端:
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>
#define SOCK_PATH "/tmp/mysocket"
int main() {
int server_fd, client_fd;
struct sockaddr_un server_addr, client_addr;
socklen_t client_len;
char buf[256];
// 创建套接字
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
// 绑定地址
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, SOCK_PATH);
unlink(SOCK_PATH); // 确保路径可用
bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 监听
listen(server_fd, 5);
// 接受连接
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
// 读取数据
int n = read(client_fd, buf, sizeof(buf));
printf("Server received: %.*s\n", n, buf);
// 关闭
close(client_fd);
close(server_fd);
unlink(SOCK_PATH);
return 0;
}
客户端:
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
#define SOCK_PATH "/tmp/mysocket"
int main() {
int sock_fd;
struct sockaddr_un addr;
// 创建套接字
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
// 连接服务器
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, SOCK_PATH);
connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
// 发送数据
write(sock_fd, "Hello Socket", 13);
// 关闭
close(sock_fd);
return 0;
}
总结比较
| IPC方式 | 特点 | 适用场景 |
|---|---|---|
| 管道 | 单向通信,有亲缘关系要求 | 父子进程简单通信 |
| 命名管道 | 可用于无亲缘关系进程 | 任意进程间通信 |
| 消息队列 | 消息边界保持,可指定消息类型 | 需要区分消息类型的场景 |
| 共享内存 | 最高效,但需要同步机制 | 大数据量频繁通信 |
| 信号量 | 主要用于进程同步 | 临界资源保护 |
| 套接字 | 最通用,支持跨网络通信 | 复杂通信需求,包括网络通信 |
7. 守护进程(Daemon)
守护进程是在后台运行的特殊进程,通常由系统启动时加载,独立于控制终端。
创建守护进程步骤:
- 调用fork()创建子进程,父进程退出
- 子进程调用setsid()创建新会话
- 改变当前工作目录到根目录
- 重设文件权限掩码
- 关闭不需要的文件描述符
- 处理SIGCHLD信号
示例代码:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
void daemonize(){
pid_t pid = fork();
if(pid < 0){
perror("fork failed");
exit(-1);
}else if(pid > 0){
exit(0);
}
//子进程继续执行
setsid();//创建新的会话
chdir("/");//改变工作目录
umask(0);//重设文件权限掩码
//关闭标准文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
//忽略SIGCHLD信号
signal(SIGCHLD, SIG_IGN);
}
int main(int argc, char const *argv[])
{
daemonize();
//守护进程实际的工作
while (1)
{
//例如定期执行的某些任务
printf("update \n");
sleep(10);
}
return 0;
}
8. 进程状态监控
/proc文件系统
Linux通过/proc虚拟文件系统提供进程信息,每个进程对应/proc/[pid]目录。
常用信息:
- /proc/[pid]/status:进程状态
- /proc/[pid]/cmdline:启动命令
- /proc/[pid]/fd/:打开的文件描述符
- /proc/[pid]/stat:详细的进程状态信息
系统调用
- getpid():获取当前进程ID
- getppid():获取父进程ID
- getpgid():获取进程组ID
- getsid():获取会话ID
9. 进程资源限制
getrlimit/setrlimit
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
常用resource参数:
- RLIMIT_CPU:CPU时间(秒)
- RLIMIT_DATA:数据段大小
- RLIMIT_STACK:栈大小
- RLIMIT_NOFILE:文件描述符数量
- RLIMIT_AS:虚拟内存大小
示例代码:
#include <stdio.h>
#include <sys/resource.h>
int main() {
struct rlimit lim;
// 获取文件描述符限制
getrlimit(RLIMIT_NOFILE, &lim);
printf("Current file descriptor limit: soft=%ld, hard=%ld\n",
(long)lim.rlim_cur, (long)lim.rlim_max);
// 设置新的限制
lim.rlim_cur = 1024;
setrlimit(RLIMIT_NOFILE, &lim);
return 0;
}
10. 进程调度
nice()系统调用
#include <unistd.h>
int nice(int inc);
- 功能:调整进程优先级
- 参数:inc为优先级增量(正值降低优先级,负值提高优先级)
- 返回值:新的nice值(-1可能表示错误)
sched_setscheduler()系统调用
#include <sched.h>
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
调度策略:
- SCHED_FIFO:先进先出实时调度
- SCHED_RR:轮转实时调度
- SCHED_OTHER:普通分时调度
示例代码:
#include <stdio.h>
#include <sched.h>
int main() {
struct sched_param param;
param.sched_priority = 10; // 设置优先级
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler failed");
return 1;
}
printf("Scheduler set to SCHED_FIFO with priority %d\n", param.sched_priority);
return 0;
}
Linux进程管理是系统编程的核心内容,理解这些概念和系统调用对于开发高效、稳定的Linux应用程序至关重要。

浙公网安备 33010602011771号