ソケットバックログ(connect)
Rev.1を表示中。最新版はこちら。
connectは接続時の接続済数がlistシステムコールによるバックログ(sk->sk_max_ack_backlog)を超えてなければ接続可能で、sk->sk_max_ack_backlog=0ならconnectは1つだけとなります。バックログを超える接続は、sk->sk_sndtimeo間sk->peer_waitでウエイトし再度接続を試み、接続できなければエラーとなります。ただし、sk->sk_sndtimeo=MAX_SCHEDULE_TIMEOUTなら、MAX_SCHEDULE_TIMEOUT間ウエイトとし再度接続の試みを永久に繰り返します。ソケットのデフォルトはMAX_SCHEDULE_TIMEOUTです。なおsetsockoptシステムコールの0での設定は、MAX_SCHEDULE_TIMEOUTが設定されます。検証サンプル
[root@localhost c]# cat unixsk_sdtime.c #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <sys/un.h> #include <time.h> #define unixsk_file "unixsk.txt" void sd_time(int sock, int time) { struct timeval send_tv; send_tv.tv_sec = time; send_tv.tv_usec = 0; setsockopt( sock, SOL_SOCKET, SO_SNDTIMEO, &send_tv, sizeof(send_tv) ); } void sever() { struct sockaddr_un sock_addr; int status, sock; sock = socket(AF_UNIX, SOCK_STREAM, 0); sock_addr.sun_family = AF_UNIX; strcpy(sock_addr.sun_path, unixsk_file); remove(sock_addr.sun_path); bind(sock, (struct sockaddr*) &sock_addr, sizeof(struct sockaddr_un)); listen(sock, 0); wait(&status); } void client(int wait) { int sock, wtime, ret; struct sockaddr_un sa; time_t time1, time2; sock = socket(AF_UNIX, SOCK_STREAM, 0); sd_time(sock, wait); sa.sun_family = AF_UNIX; strcpy(sa.sun_path, unixsk_file); ret = connect(sock, (struct sockaddr*) &sa, sizeof(struct sockaddr_un)); printf("returned connect1:%d\n", ret); time(&time1); ret = connect(sock, (struct sockaddr*) &sa, sizeof(struct sockaddr_un)); time(&time2); wtime = (int)difftime(time2,time1); printf("returned connect2:%d wate time:%d\n", ret, wtime); close(sock); } void main(int argc, char * argv[]) { if (fork()) { sever(); } else { sleep(1); client(atoi(argv[1])); } }結果
[root@localhost c]# ./unixsk_sdtime.o -1 <-------sk->sk_sndtimeo=0でウエイトせずエラー。 returned connect1:0 returned connect2:-1 wate time:0 [root@localhost c]# ./unixsk_sdtime.o 0 <-------sk->sk_sndtimeo=MAX_SCHEDULE_TIMEOUTで永久にウエイト。 returned connect1:0 ^C [root@localhost c]# ./unixsk_sdtime.o 1 <-------sk->sk_sndtimeo=1で1秒ウエイトしエラー。 returned connect1:0 returned connect2:-1 wate time:1
実装
connectの待機はschedule_timeout(sk->sk_sndtimeo)がコールされ、MAX_SCHEDULE_TIMEOUTならサーバプロセスからaccess下から起床されるまで待機し、マイナスなら待機せず、そうでないなら其の時間待機し、待機した分を差し引いた時間(通常は0となりエラー)を次期待機時間とし再度接続を試みます。#define MAX_SCHEDULE_TIMEOUT LONG_MAX signed long __sched schedule_timeout(signed long timeout) { struct timer_list timer; unsigned long expire; switch (timeout) { case MAX_SCHEDULE_TIMEOUT: schedule(); goto out; default: if (timeout < 0) { printk(KERN_ERR "schedule_timeout: wrong timeout " "value %lx\n", timeout); dump_stack(); current->state = TASK_RUNNING; goto out; } } expire = timeout + jiffies; setup_timer_on_stack(&timer, process_timeout, (unsigned long)current); __mod_timer(&timer, expire, false, TIMER_NOT_PINNED); schedule(); del_singleshot_timer_sync(&timer); destroy_timer_on_stack(&timer); timeout = expire - jiffies; out: return timeout < 0 ? 0 : timeout; }setsockoptシステムコールの引数level=SOL_SOCKET/optname=SO_SNDTIMEOで、long *timeo_p引数を&sk->sk_sndtimeoでコールされます。tv.tv_sec < 0なら、sk->sk_sndtimeo=0で、tv.tv_sec == 0 && tv.tv_usec == 0なら、sk->sk_sndtimeo=MAX_SCHEDULE_TIMEOUT、そうでないなら掛かる時間が設定されます。
static int sock_set_timeout(long *timeo_p, char __user *optval, int optlen) { struct timeval tv; if (optlen < sizeof(tv)) return -EINVAL; if (copy_from_user(&tv, optval, sizeof(tv))) return -EFAULT; if (tv.tv_usec < 0 || tv.tv_usec >= USEC_PER_SEC) return -EDOM; if (tv.tv_sec < 0) { static int warned __read_mostly; *timeo_p = 0; if (warned < 10 && net_ratelimit()) { warned++; printk(KERN_INFO "sock_set_timeout: `%s' (pid %d) " "tries to set negative timeout\n", current->comm, task_pid_nr(current)); } return 0; } *timeo_p = MAX_SCHEDULE_TIMEOUT; if (tv.tv_sec == 0 && tv.tv_usec == 0) return 0; if (tv.tv_sec < (MAX_SCHEDULE_TIMEOUT/HZ - 1)) *timeo_p = tv.tv_sec*HZ + (tv.tv_usec+(1000000/HZ-1))/(1000000/HZ); return 0; }ソケット初期化のソケット作成時にコールされ、sk->sk_rcvtimeo=MAX_SCHEDULE_TIMEOUT/sk->sk_sndtimeo=MAX_SCHEDULE_TIMEOUTとなります。
void sock_init_data(struct socket *sock, struct sock *sk) { skb_queue_head_init(&sk->sk_receive_queue); skb_queue_head_init(&sk->sk_write_queue); skb_queue_head_init(&sk->sk_error_queue); #ifdef CONFIG_NET_DMA skb_queue_head_init(&sk->sk_async_wait_queue); #endif sk->sk_send_head = NULL; init_timer(&sk->sk_timer); sk->sk_allocation = GFP_KERNEL; sk->sk_rcvbuf = sysctl_rmem_default; sk->sk_sndbuf = sysctl_wmem_default; sk->sk_state = TCP_CLOSE; sk_set_socket(sk, sock); sock_set_flag(sk, SOCK_ZAPPED); if (sock) { sk->sk_type = sock->type; sk->sk_wq = sock->wq; sock->sk = sk; } else sk->sk_wq = NULL; spin_lock_init(&sk->sk_dst_lock); rwlock_init(&sk->sk_callback_lock); lockdep_set_class_and_name(&sk->sk_callback_lock, af_callback_keys + sk->sk_family, af_family_clock_key_strings[sk->sk_family]); sk->sk_state_change = sock_def_wakeup; sk->sk_data_ready = sock_def_readable; sk->sk_write_space = sock_def_write_space; sk->sk_error_report = sock_def_error_report; sk->sk_destruct = sock_def_destruct; sk->sk_sndmsg_page = NULL; sk->sk_sndmsg_off = 0; sk->sk_peer_pid = NULL; sk->sk_peer_cred = NULL; sk->sk_write_pending = 0; sk->sk_rcvlowat = 1; sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT; sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT; sk->sk_stamp = ktime_set(-1L, 0); smp_wmb(); atomic_set(&sk->sk_refcnt, 1); atomic_set(&sk->sk_drops, 0); }