Day1:进程的创建和回收
1、实现一个进程链,父进程->子进程->孙进程->重孙进程->重重孙进程
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int i;
pid_t pid;
for (i = 1; i <= 4; i++) {
pid = fork();
if (pid == 0) {
printf("I am the %dth child process (pid=%d)\n", i, getpid());
} else if (pid > 0) {
wait(NULL);
break;
} else {
perror("fork");
exit(1);
}
}
if (i == 5) {
printf("I am the parent process (pid=%d)\n", getpid());
}
return 0;
}
Day2:exec函数族
1、用exec函数族函数实现 "ls -l -a" 命令
#include <stdio.h>
#include <unistd.h>
int main()
{
char *agv[] = {"ls", "-l", "-a", NULL};
if(execvp("ls", agv) < 0){
perror("execvp");
}
return 0;
}
Day3:守护进程
1、写出创建守护进程的详细步骤
创建守护进程的步骤如下:
(1)创建一个子进程,并使父进程退出,使子进程成为孤儿进程,从而被init进程接管。
(2)在子进程中调用setsid函数创建一个新的会话,并使子进程成为该会话的首进程和组长进程,从而与控制终端和父进程分离。
(3)改变当前工作目录,以避免守护进程的工作目录在启动时可能被其他进程改变而出现问题,守护进程一直在后台运行,其工作目录不能被卸载。
(4)使用umask函数重设文件权限掩码,文件权限掩码设置为0,只影响当前进程
(5)关闭所有文件描述符,因为守护进程不再需要与控制终端进行交互,所以也不需要与文件描述符相关联。关闭所有从父进程继承的打开文件,对于已脱离终端的进程,标准输入、标准输出、标准错误输出无法再使用
Day4:GDB 调试多进程程序
1、列举三个GDB调试多进程程序的命令及其含义
set follow-fork-mode child 设置GDB调试子进程
set follow-fork-mode parent 设置GDB调试父进程
默认情况下,follow-fork-mode设置为parent。
set detach-on-fork on/off 设置GDB跟踪调试单个进程或多个
on: 只调试父进程或子进程的其中一个,(根据follow-fork-mode来决定),这是默认的模式
off:父子进程都在gdb的控制之下,其中一个进程正常调试(根据follow-fork-mode来决定),另一个进程会被设置为暂停状态。
info inferiors 显示GDB调试的进程
inferiors 进程序号(1,2,3....) 切换GDB调试的进程
Day5:线程的创建和参数传递
1、使用pthread_create实现 10 个子线程,并且让每个子线程打印自己的线程号
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *testThread(void *arg){
printf("This is a thread test,pid=%d,tid=%lu\n",getpid(),pthread_self());
printf("This is %d thread.\n", (int)arg + 1);
}
int main(){
pthread_t tid[10];
int ret;
int i;
for(i=0;i<10;i++){
ret = pthread_create(&tid[i],NULL,testThread,(void *)i);
// sleep(1);
printf("This is main thread,tid=%lu\n",tid[i]);
}
}
Day6:线程的回收及内存演示
1、编写一个多线程程序,实现线程的回收
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *func(void *arg){
pthread_detach(pthread_self());
printf("This is child thread\n");
sleep(2);
pthread_exit("thread return");
}
int main(){
pthread_t tid[5];
void *retv;
int i;
for(i=0;i<5;i++){
pthread_create(&tid[i],NULL,func,NULL);
}
while(1){
sleep(1);
}
}
Day7:线程的取消和清理
1、void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute) 的本质是什么?
A. 两个函数
B. pthread_cleanup_push函数可以取消一个线程
C. pthread_cleanup_pop函数可以清理一个线程
D. 两个宏定义,必须配对使用
D 这两个函数实际上是宏定义,必须配对使用。在编译时会被展开成一些代码,用于在线程退出时执行一些清理工作,如释放资源等。
pthread_cleanup_push宏定义用于注册一个清理函数,pthread_cleanup_pop用于取消注册的清理函数。
这两个宏必须成对使用,且每个pthread_cleanup_push对应一个pthread_cleanup_pop。
execute参数用于控制是否执行清理函数,为非0值时执行,为0时不执行。
Day8:互斥锁/读写锁的概念及使用
1、实现多个线程写一个文件,使用互斥锁
#include <stdio.h>
#include <pthread.h>
#define NUM 10
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* thread_func(void* arg){
FILE* fp = (FILE*)arg;
pthread_mutex_lock(&mutex);
fprintf(fp, "This is from thread %ld!\n", pthread_self());
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main(){
FILE* fp = fopen("test.txt", "a+");
int i;
if(fp == NULL){
perror("fopen");
return -1;
}
pthread_t tid[NUM];
for(i = 0; i < NUM; i++){
pthread_create(&tid[i], NULL, thread_func, (void*)fp);
}
for(i = 0; i < NUM; i++){
pthread_join(tid[i], NULL);
}
fclose(fp);
return 0;
}
Day9:条件变量的使用及注意事项
1、条件变量有两种初始化的方式,写出这两种方式:
静态方式
静态初始化:使用PTHREAD_COND_INITIALIZER常量可以在定义条件变量的同时进行初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态方式
动态初始化:使用pthread_cond_init函数可以在运行时进行初始化
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
Day10:线程池及gdb调试多线程
1、实现线程池代码
//包含pthread库,用于多线程编程
#include <pthread.h>
//包含stdio库,用于标准输入输出
#include <stdio.h>
//包含unistd库,用于sleep函数
#include <unistd.h>
//包含stdlib库,用于动态内存分配
#include <stdlib.h>
//定义线程池中线程的数量
#define POOL_NUM 10
//定义任务结构体
typedef struct Task{
void *(*func)(void *arg); //任务函数指针
void *arg; //任务参数
struct Task *next; //下一个任务指针
}Task;
//定义线程池结构体
typedef struct ThreadPool{
pthread_mutex_t taskLock; //任务锁
pthread_cond_t newTask; //新任务条件变量
pthread_t tid[POOL_NUM]; //线程ID数组
Task *queue_head; //任务队列头部指针
int busywork; //忙碌的线程数量
}ThreadPool;
ThreadPool *pool; //全局线程池指针
//工作线程函数
void *workThread(void *arg){
while(1){
//加锁
pthread_mutex_lock(&pool->taskLock);
//等待新任务到来
pthread_cond_wait(&pool->newTask,&pool->taskLock);
Task *ptask = pool->queue_head;
//取出队列头部任务
pool->queue_head = pool->queue_head->next;
//解锁
pthread_mutex_unlock(&pool->taskLock);
//执行任务函数
ptask->func(ptask->arg);
//忙碌线程数减1
pool->busywork--;
}
}
//实际的工作函数
void *realwork(void *arg){
printf("Finish work %d\n",(int)arg);
}
//向线程池中添加任务
void pool_add_task(int arg){
Task *newTask;
//加锁
pthread_mutex_lock(&pool->taskLock);
//当忙碌线程数达到线程池数量时等待
while(pool->busywork>=POOL_NUM){
pthread_mutex_unlock(&pool->taskLock);
usleep(10000); //等待10毫秒
pthread_mutex_lock(&pool->taskLock);
}
pthread_mutex_unlock(&pool->taskLock);
//分配新任务
newTask = malloc(sizeof(Task));
newTask->func = realwork; //实际的工作函数
newTask->arg = arg;
//加锁
pthread_mutex_lock(&pool->taskLock);
Task *member = pool->queue_head;
if(member==NULL){
//队列为空时,将新任务作为队列头部
pool->queue_head = newTask;
}else{
//将新任务加入队列尾部
while(member->next!=NULL){
member=member->next;
}
member->next = newTask;
}
//忙碌线程数加1
pool->busywork++;
//通知工作线程有新任务到来
pthread_cond_signal(&pool->newTask);
//解锁
pthread_mutex_unlock(&pool->taskLock);
}
//初始化线程池
void pool_init(){
pool = malloc(sizeof(ThreadPool));
pthread_mutex_init(&pool->taskLock,NULL); //初始化任务锁
pthread_cond_init(&pool->newTask,NULL); //初始化新任务条件变量
pool->queue_head = NULL; //初始化任务队列头部
pool->busywork=0; //初始化忙碌线程数为0
//创建线程池中的线程
for(int i=0;i<POOL_NUM;i++){
pthread_create(&pool->tid[i],NULL,workThread,NULL);
}
}
//销毁线程池
void pool_destory(){
Task *head;
//清空任务队列
while(pool->queue_head!=NULL){
head = pool->queue_head;
pool->queue_head = pool->queue_head->next;
free(head);
}
//销毁任务锁
pthread_mutex_destroy(&pool->taskLock);
//销毁新任务条件变量
pthread_cond_destroy(&pool->newTask);
//释放线程池内存
free(pool);
}
int main(){
pool_init(); //初始化线程池
sleep(20); //休眠20秒
for(int i=1;i<=20;i++){
pool_add_task(i); //向线程池中添加任务
}
sleep(5); //休眠5秒
pool_destory(); //销毁线程池
return 0;
}
Day11:有名管道和无名管道
无名管道的读写特性有哪些?
无名管道是一种半双工的管道,数据只能单向流动,即只能从管道的写端流向读端。
无名管道的数据读取是阻塞的,如果读取端没有数据可读,则读取操作将一直阻塞,直到有数据可读为止。
无名管道的写入操作也是阻塞的,如果写入端的管道已经满了(缓冲区已满),则写入操作将一直阻塞,直到管道中有空间可写为止。
无名管道是基于内存的,数据不会被写入磁盘进行持久化,因此管道只能在相互关联的进程之间使用。
无名管道的数据是按顺序读取的,即先写入管道的数据先被读取。
无名管道的数据是不可重复读取的,即一旦被读取,就不能再次读取同样的数据。
读管道:
管道中有数据:
read返回实际读到的字节数。
管道中无数据:
管道写端被全部关闭,read返回0 (好像读到文件结尾)
写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
写管道:
管道读端全部被关闭:
进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
管道读端没有全部关闭:
管道已满,write阻塞。(管道大小64K)
管道未满,write将数据写入,并返回实际写入的字节数。
Day12:共享内存
实现两个进程使用内存映射通信程序
mmap_r.c
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR); // 打开文件"test",返回文件描述符fd
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END); // 获取文件长度,用于mmap函数的第二个参数
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // 将文件"test"映射到共享内存中,返回映射后的地址addr
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd); // 关闭文件描述符fd
while(1){
printf("read=%s\n",(char*)(addr)); // 循环读取共享内存中的内容并打印
sleep(1); // 每次等待1秒钟
}
return 0;
}
mmap_w.c
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
void *addr;
int fd;
fd =open("test",O_RDWR); // 打开文件"test",返回文件描述符fd
if(fd<0){
perror("open");
return 0;
}
int len = lseek(fd,0,SEEK_END); // 获取文件长度,用于mmap函数的第二个参数
addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // 将文件"test"映射到共享内存中,返回映射后的地址addr
if(addr == MAP_FAILED){
perror("mmap");
return 0;
}
close(fd); // 关闭文件描述符fd
while(1){
printf("read=%s\n",(char*)(addr)); // 循环读取共享内存中的内容并打印
sleep(1); // 每次等待1秒钟
}
return 0;
}
Day13:信号机制上
实现捕捉SIGINT信号,在屏幕上打印 "Ctrl + c"
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>
typedef void (*sighandler_t)(int); // 定义一个函数指针类型sighandler_t,该函数指针指向一个参数为int类型、返回值为void类型的函数
sighandler_t oldact; // 定义一个sighandler_t类型的变量oldact
void handle(int sig){ // 定义一个处理函数handle,参数为信号编号sig
printf("Ctrl + c \n"); // 打印字符串
signal(SIGINT,oldact); // 恢复原有的信号处理函数
}
int main(){
oldact = signal(SIGINT,handle); // 将SIGINT信号的处理函数设置为handle,并将原有的信号处理函数保存到oldact中
while(1){ // 死循环
sleep(1); // 每次等待1秒钟
}
return 0;
}
Day14:信号机制下
实现信号回收子进程程序
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
void sigchld_handler(int signum)
{
int status;
pid_t pid;
// 循环等待所有子进程退出
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("child process %d exited with status %d\n", pid, WEXITSTATUS(status));
}
}
int main()
{
pid_t pid;
struct sigaction sa;
// 安装信号处理函数
sa.sa_handler = sigchld_handler; // 设置信号处理函数
sigemptyset(&sa.sa_mask); // 初始化信号集
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; // 重新启动系统调用,不处理停止的子进程
if (sigaction(SIGCHLD, &sa, NULL) == -1) { // 安装SIGCHLD信号的处理函数
perror("sigaction");
return 1;
}
// 创建子进程
pid = fork(); // 创建子进程
if (pid == -1) {
perror("fork");
return 1;
} else if (pid == 0) {
// 子进程
printf("child process %d started\n", getpid());
sleep(3);
printf("child process %d exited\n", getpid());
exit(0);
} else {
// 父进程
printf("parent process %d started and created child process %d\n", getpid(), pid);
// 等待子进程退出
while (1) {
sleep(1);
}
return 0;
}
}
Day15:消息队列
编写实现两个进程实现消息队列通信的程序
msgsnd.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct{
long msg_type; // 消息类型
char buf[128]; // 消息内容
}msgT;
#define MSGLEN (sizeof(msgT)-sizeof(long)) // 定义消息长度
int main(){
key_t key;
int msgid;
int ret;
msgT msg;
key = ftok(".",100); // 生成IPC键值
if(key<0){
perror("ftok");
return 0;
}
msgid = msgget(key,IPC_CREAT|0666); // 创建消息队列
if(msgid<0){
perror("msgget");
return 0;
}
msg.msg_type = 1; // 设置消息类型
strcpy(msg.buf,"this msg type 1"); // 设置消息内容
ret = msgsnd(msgid,&msg,MSGLEN,0); // 发送消息
if(ret<0){
perror("msgsnd");
return 0;
}
msg.msg_type = 2;
strcpy(msg.buf,"this msg type 2");
ret = msgsnd(msgid,&msg,MSGLEN,0);
if(ret<0){
perror("msgsnd");
return 0;
}
msg.msg_type = 3;
strcpy(msg.buf,"this msg type 3");
ret = msgsnd(msgid,&msg,MSGLEN,0);
if(ret<0){
perror("msgsnd");
return 0;
}
msg.msg_type = 4;
strcpy(msg.buf,"this msg type 4");
ret = msgsnd(msgid,&msg,MSGLEN,0);
if(ret<0){
perror("msgsnd");
return 0;
}
msg.msg_type = 5;
strcpy(msg.buf,"this msg type 5");
ret = msgsnd(msgid,&msg,MSGLEN,0);
if(ret<0){
perror("msgsnd");
return 0;
}
}
msgrcv.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
typedef struct{
long msg_type; // 消息类型
char buf[128]; // 消息内容
}msgT;
#define MSGLEN (sizeof(msgT)-sizeof(long)) // 定义消息长度
int main(){
int msgid;
key_t key;
msgT msg;
int ret;
key = ftok(".",100); // 生成IPC键值
if(key<0){
perror("ftok");
return 0;
}
msgid = msgget(key,IPC_CREAT|0666); // 创建消息队列
if(msgid<0){
perror("msgget");
return 0;
}
int count=0;
while(1){ // 循环接收消息
ret = msgrcv(msgid,&msg,MSGLEN,0,0); // 接收消息
if(ret<0){
perror("msgrcv");
return 0;
}
count++;
if(count>3){ // 接收三个消息后结束循环
break;
}
printf("receiv msg type=%d,buf=%s\n",(int)msg.msg_type,msg.buf); // 输出接收到的消息
}
ret = msgctl(msgid,IPC_RMID,NULL); // 删除消息队列
if(ret<0){
perror("msgctl");
return 0;
}
}
Day16:信号灯
编程实现无名信号灯
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <pthread.h>
// 定义读写信号量和共享内存地址
sem_t sem_r,sem_w;
char *shmaddr;
// 信号处理函数,销毁信号量并退出程序
void destroysem(int sig){
sem_destroy(&sem_r);
sem_destroy(&sem_w);
exit(0);
}
// 子线程函数,循环读取共享内存并输出
void *readmem(void *arg){
while(1){
sem_wait(&sem_r); // 等待读信号量
printf("%s\n",shmaddr); // 输出共享内存内容
sem_post(&sem_w); // 发送写信号量
}
}
int main(){
key_t key;
int shmid;
// 注册SIGINT信号处理函数
struct sigaction act;
act.sa_handler = destroysem;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key = ftok(".",100); // 生成IPC键值
if(key<0){
perror("ftok");
return 0;
}
shmid = shmget(key,500,0666|IPC_CREAT); // 获取/创建共享内存
if(shmid<0){
perror("shmget");
return 0;
}
shmaddr = shmat(shmid,NULL,0); // 映射共享内存地址
sem_init(&sem_r,0,0); // 初始化读信号量为0
sem_init(&sem_w,0,1); // 初始化写信号量为1
pthread_t tid;
pthread_create(&tid,NULL,readmem,NULL); // 创建子线程
while(1){
sem_wait(&sem_w); // 等待写信号量
printf(">");
fgets(shmaddr,500,stdin); // 从标准输入读取内容并写入共享内存
sem_post(&sem_r); // 发送读信号量
}
}